Merge pull request #2 from HideyoshiNakazone/devel

v0.0.2: Adds Implementation of  OAuth2 Authentication and Better Drag Event Behavior
This commit is contained in:
2022-11-10 00:56:39 -03:00
committed by GitHub
21 changed files with 1619 additions and 1152 deletions

2431
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,12 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^14.0.3", "@angular/animations": "^14.0.3",
"@angular/common": "^14.0.0", "@angular/cdk": "^14.2.6",
"@angular/common": "^14.2.9",
"@angular/compiler": "^14.0.0", "@angular/compiler": "^14.0.0",
"@angular/core": "^14.0.0", "@angular/core": "^14.2.9",
"@angular/forms": "^14.0.0", "@angular/forms": "^14.0.0",
"@angular/material": "^14.2.6",
"@angular/platform-browser": "^14.0.0", "@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0", "@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0", "@angular/router": "^14.0.0",

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component'; import { HomeComponent } from './home/home.component';
import { CallbackComponent } from './header/header-popup/callback/callback.component';
const routes: Routes = [ const routes: Routes = [
{ {
@@ -12,6 +13,10 @@ const routes: Routes = [
{ {
path: 'home', path: 'home',
component: HomeComponent component: HomeComponent
},
{
path: 'callback',
component: CallbackComponent
} }
] ]

View File

@@ -25,8 +25,6 @@ import { environment } from '../environments/environment';
FontAwesomeModule, FontAwesomeModule,
ServiceWorkerModule.register('ngsw-worker.js', { ServiceWorkerModule.register('ngsw-worker.js', {
enabled: environment.production, enabled: environment.production,
// Register the ServiceWorker as soon as the application is stable
// or after 30 seconds (whichever comes first).
registrationStrategy: 'registerWhenStable:30000' registrationStrategy: 'registerWhenStable:30000'
}) })
], ],

View File

@@ -0,0 +1 @@
<p>callback works!</p>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CallbackComponent } from './callback.component';
describe('CallbackComponent', () => {
let component: CallbackComponent;
let fixture: ComponentFixture<CallbackComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CallbackComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(CallbackComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,41 @@
import { ActivatedRoute, Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { AuthService } from 'src/app/shared/auth/auth.service';
@Component({
selector: 'app-callback',
templateUrl: './callback.component.html',
styleUrls: ['./callback.component.css']
})
export class CallbackComponent implements OnInit {
constructor(private route: ActivatedRoute,
private router: Router,
private authService: AuthService) { }
ngOnInit(): void {
this.route.queryParams.subscribe(p => {
let auth: 'google' | 'github' = p['auth'];
if (auth === 'google') {
this.loginGoogle(p);
} else if (auth === 'github') {
this.loginGithub(p);
}
this.router.navigate(['/home'])
})
}
private loginGoogle(p: any) {
this.authService.loginGoogleUser(p)
}
private loginGithub(p: any) {
this.authService.loginGithubUser(p)
}
}

View File

@@ -38,6 +38,13 @@
display: flex; display: flex;
} }
.oauth-button {
height: 50px;
width: 250px;
border: #ffffff;
background-color: #ffffff;
}
.separator-line { .separator-line {
justify-content: center; justify-content: center;
align-content: center; align-content: center;

View File

@@ -36,9 +36,20 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
<div class="col-lg-6 authentication-body"> <div class="col-lg-6 authentication-body">
<p>Google</p> <button mat-button
<p>Linkedin</p> class="oauth-button d-flex justify-content-center align-items-center"
<p>Github</p> (click)="onGoogleLogin()">
<mat-icon style="width: 50px; height:30px"
svgIcon="google-logo"></mat-icon>
Login With Google
</button>
<button mat-button
class="oauth-button d-flex justify-content-center align-items-center"
(click)="onGithubLogin()">
<mat-icon style="width: 50px; height:30px"
svgIcon="github-logo"></mat-icon>
Login With Github
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,7 @@
import { AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { AfterViewInit, ChangeDetectorRef, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { faLock, faUser } from '@fortawesome/free-solid-svg-icons'; import { faLock, faUser } from '@fortawesome/free-solid-svg-icons';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { AuthService } from 'src/app/shared/auth/auth.service'; import { AuthService } from 'src/app/shared/auth/auth.service';
@@ -8,6 +10,10 @@ import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker';
import UserChecker from 'src/app/shared/model/user/user.checker'; import UserChecker from 'src/app/shared/model/user/user.checker';
import { User } from 'src/app/shared/model/user/user.model'; import { User } from 'src/app/shared/model/user/user.model';
const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg";
const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
templateUrl: './login.component.html', templateUrl: './login.component.html',
@@ -16,13 +22,13 @@ import { User } from 'src/app/shared/model/user/user.model';
export class LoginComponent implements OnInit, AfterViewInit, OnDestroy { export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() @Input()
state: boolean = false; state: boolean = false;
@Input() @Input()
ignoreClickOutside!: HTMLDivElement[]; ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
popupState = false; popupState = false;
@@ -30,7 +36,7 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
authSubject!: Subscription; authSubject!: Subscription;
errorMessage!: string|null; errorMessage!: string | null;
_userIcon = faUser; _userIcon = faUser;
@@ -38,13 +44,23 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
constructor( constructor(
private authService: AuthService, private authService: AuthService,
private changeDetectorRef: ChangeDetectorRef private changeDetectorRef: ChangeDetectorRef,
) { } private matIconRegistry: MatIconRegistry,
private domSanitizer: DomSanitizer) {
this.matIconRegistry.addSvgIcon(
"google-logo",
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG)
);
this.matIconRegistry.addSvgIcon(
"github-logo",
this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG)
);
}
ngOnInit(): void { ngOnInit(): void {
this.loginForm = new FormGroup({ this.loginForm = new FormGroup({
'username' : new FormControl(null, [Validators.required]), 'username': new FormControl(null, [Validators.required]),
'password' : new FormControl(null, [Validators.required]) 'password': new FormControl(null, [Validators.required])
}); });
this.errorMessage = null; this.errorMessage = null;
this.authSubject = this.authService.authSubject.subscribe( this.authSubject = this.authService.authSubject.subscribe(
@@ -76,7 +92,15 @@ export class LoginComponent implements OnInit, AfterViewInit, OnDestroy {
} }
private validateLogin(res: User|HttpError|null) { onGoogleLogin() {
this.authService.googleLogin();
}
onGithubLogin() {
this.authService.githubLogin();
}
private validateLogin(res: User | HttpError | null) {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.closePopup() this.closePopup()
} if (HttpErrorChecker.test(res)) { } if (HttpErrorChecker.test(res)) {

View File

@@ -42,6 +42,14 @@
display: flex; display: flex;
} }
.oauth-button {
height: 50px;
width: 250px;
border: #ffffff;
background-color: #ffffff;
}
.separator-line { .separator-line {
justify-content: center; justify-content: center;
align-content: center; align-content: center;

View File

@@ -52,9 +52,20 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
<div class="col-lg-6 auth-body auth-body-links"> <div class="col-lg-6 auth-body auth-body-links">
<p>Google</p> <button mat-button
<p>Linkedin</p> class="oauth-button d-flex justify-content-center align-items-center"
<p>Github</p> (click)="onGoogleLogin()">
<mat-icon style="width: 50px; height:30px"
svgIcon="google-logo"></mat-icon>
Login With Google
</button>
<button mat-button
class="oauth-button d-flex justify-content-center align-items-center"
(click)="onGithubLogin()">
<mat-icon style="width: 50px; height:30px"
svgIcon="github-logo"></mat-icon>
Login With Github
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,7 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
import { faEnvelope, faFingerprint, faLock, faUser } from '@fortawesome/free-solid-svg-icons'; import { faEnvelope, faFingerprint, faLock, faUser } from '@fortawesome/free-solid-svg-icons';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { AuthService } from 'src/app/shared/auth/auth.service'; import { AuthService } from 'src/app/shared/auth/auth.service';
@@ -8,6 +10,10 @@ import HttpErrorChecker from 'src/app/shared/model/httpError/httpErrorChecker';
import UserChecker from 'src/app/shared/model/user/user.checker'; import UserChecker from 'src/app/shared/model/user/user.checker';
import { User } from 'src/app/shared/model/user/user.model'; import { User } from 'src/app/shared/model/user/user.model';
const GOOGLE_LOGO_SVG = "assets/img/providers/google.svg";
const GITHUB_LOGO_SVG = "assets/img/providers/github.svg";
@Component({ @Component({
selector: 'app-signup', selector: 'app-signup',
templateUrl: './signup.component.html', templateUrl: './signup.component.html',
@@ -16,19 +22,19 @@ import { User } from 'src/app/shared/model/user/user.model';
export class SignupComponent implements OnInit { export class SignupComponent implements OnInit {
@Input() @Input()
state: boolean = false; state: boolean = false;
@Input() @Input()
ignoreClickOutside!: HTMLDivElement[]; ignoreClickOutside!: HTMLDivElement[];
@Output() @Output()
stateChange = new EventEmitter<boolean>(); stateChange = new EventEmitter<boolean>();
signupForm!: FormGroup; signupForm!: FormGroup;
authSubject!: Subscription; authSubject!: Subscription;
errorMessage!: string|null; errorMessage!: string | null;
_fullnameIcon = faFingerprint; _fullnameIcon = faFingerprint;
@@ -38,7 +44,18 @@ export class SignupComponent implements OnInit {
_passwordIcon = faLock; _passwordIcon = faLock;
constructor(private authService: AuthService) { } constructor(private authService: AuthService,
private matIconRegistry: MatIconRegistry,
private domSanitizer: DomSanitizer) {
this.matIconRegistry.addSvgIcon(
"google-logo",
this.domSanitizer.bypassSecurityTrustResourceUrl(GOOGLE_LOGO_SVG)
);
this.matIconRegistry.addSvgIcon(
"github-logo",
this.domSanitizer.bypassSecurityTrustResourceUrl(GITHUB_LOGO_SVG)
);
}
ngOnInit(): void { ngOnInit(): void {
this.signupForm = new FormGroup({ this.signupForm = new FormGroup({
@@ -63,7 +80,7 @@ export class SignupComponent implements OnInit {
onSignUp() { onSignUp() {
let user: User = { let user: User = {
fullname: this.signupForm.controls['fullname'].value, name: this.signupForm.controls['fullname'].value,
email: this.signupForm.controls['email'].value, email: this.signupForm.controls['email'].value,
username: this.signupForm.controls['username'].value, username: this.signupForm.controls['username'].value,
password: this.signupForm.controls['password'].value password: this.signupForm.controls['password'].value
@@ -71,7 +88,15 @@ export class SignupComponent implements OnInit {
this.authService.signup(user); this.authService.signup(user);
} }
private validateSignup(res: User|HttpError|null) { onGoogleLogin() {
this.authService.googleLogin();
}
onGithubLogin() {
this.authService.githubLogin();
}
private validateSignup(res: User | HttpError | null) {
if (res && UserChecker.test(res)) { if (res && UserChecker.test(res)) {
this.closePopup() this.closePopup()
} if (HttpErrorChecker.test(res)) { } if (HttpErrorChecker.test(res)) {

View File

@@ -97,6 +97,7 @@ export class HeaderComponent {
this.navSliderStatus = false; this.navSliderStatus = false;
this.userSliderStatus = false; this.userSliderStatus = false;
this.profileDropdownState = false;
} }
private createSignupPopup() { private createSignupPopup() {
@@ -119,6 +120,7 @@ export class HeaderComponent {
this.navSliderStatus = false; this.navSliderStatus = false;
this.userSliderStatus = false; this.userSliderStatus = false;
this.profileDropdownState = false;
} }
private closeLoginPopup() { private closeLoginPopup() {

View File

@@ -13,6 +13,8 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { LoginComponent } from './header-popup/login/login.component'; import { LoginComponent } from './header-popup/login/login.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SignupComponent } from './header-popup/signup/signup.component'; import { SignupComponent } from './header-popup/signup/signup.component';
import { CallbackComponent } from './header-popup/callback/callback.component';
import {MatIconModule} from '@angular/material/icon';
@@ -24,7 +26,8 @@ import { SignupComponent } from './header-popup/signup/signup.component';
UserSliderComponent, UserSliderComponent,
HeaderDropdownComponent, HeaderDropdownComponent,
LoginComponent, LoginComponent,
SignupComponent SignupComponent,
CallbackComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@@ -33,6 +36,7 @@ import { SignupComponent } from './header-popup/signup/signup.component';
FontAwesomeModule, FontAwesomeModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
MatIconModule,
SharedModule SharedModule
], exports: [ ], exports: [
HeaderComponent, HeaderComponent,

View File

@@ -1,7 +1,8 @@
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { first, firstValueFrom, Observable, Subject, take } from 'rxjs'; import { first, firstValueFrom, Observable, Subject, take } from 'rxjs';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { param } from 'ts-interface-checker';
import { HttpError } from '../model/httpError/httpError.model'; import { HttpError } from '../model/httpError/httpError.model';
import { Token } from '../model/token/token.model'; import { Token } from '../model/token/token.model';
import { User } from '../model/user/user.model'; import { User } from '../model/user/user.model';
@@ -13,7 +14,7 @@ export class AuthService {
private userAuthenticated!: User; private userAuthenticated!: User;
authSubject = new Subject<User|HttpError|null>(); authSubject = new Subject<User | HttpError | null>();
readonly BACKEND_PATH = environment.backendPath; readonly BACKEND_PATH = environment.backendPath;
@@ -23,6 +24,22 @@ export class AuthService {
this.validateUser(this.loginUser(userAuthAtempt)); this.validateUser(this.loginUser(userAuthAtempt));
} }
googleLogin() {
window.open(this.BACKEND_PATH + '/oauth2/authorization/google', '_self');
}
githubLogin() {
window.open(this.BACKEND_PATH + '/oauth2/authorization/github', '_self');
}
loginGoogleUser(p: any): void {
this.validateUser(this.fetchGoogleOAuthToken(p))
}
loginGithubUser(p: any): void {
this.validateUser(this.fetchGithubOAuthToken(p))
}
signup(userAuthAtempt: User): void { signup(userAuthAtempt: User): void {
this.validateUser(this.createUser(userAuthAtempt)); this.validateUser(this.createUser(userAuthAtempt));
@@ -39,8 +56,8 @@ export class AuthService {
async getUserAccessToken(): Promise<Token | undefined> { async getUserAccessToken(): Promise<Token | undefined> {
if (this.userAuthenticated) { if (this.userAuthenticated) {
if ((!this.userAuthenticated.accessToken && this.refreshAccessToken ) || if ((!this.userAuthenticated.accessToken && this.refreshAccessToken) ||
(this.userAuthenticated.accessToken && this.userAuthenticated.accessToken.expirationDate < Date.now())) { (this.userAuthenticated.accessToken && this.userAuthenticated.accessToken.expirationDate < Date.now())) {
this.userAuthenticated = <User>(await this.refreshAccessToken()); this.userAuthenticated = <User>(await this.refreshAccessToken());
} }
return this.userAuthenticated.accessToken; return this.userAuthenticated.accessToken;
@@ -66,6 +83,44 @@ export class AuthService {
) )
} }
private fetchGoogleOAuthToken(p: any): Observable<User|any> {
let params = new HttpParams(
{
fromObject: p
}
);
return this.http.get<User>(
this.BACKEND_PATH + '/login/oauth2/code/google',
{
withCredentials: true,
params: params
},
).pipe(
first()
);
}
private fetchGithubOAuthToken(p: any): Observable<User|any> {
let params = new HttpParams(
{
fromObject: p
}
);
return this.http.get<User>(
this.BACKEND_PATH + '/login/oauth2/code/github',
{
withCredentials: true,
params: params
},
).pipe(
first()
);
}
private createUser(newUser: User) { private createUser(newUser: User) {
return this.http.post<User>( return this.http.post<User>(
this.BACKEND_PATH + "/user/signup", this.BACKEND_PATH + "/user/signup",

View File

@@ -1,6 +1,6 @@
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output, ViewChild } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, EventEmitter, Inject, Input, OnDestroy, Output, ViewChild } from '@angular/core';
import { filter, fromEvent, Subscription } from 'rxjs'; import { combineLatest, combineLatestWith, filter, fromEvent, merge, Subscription } from 'rxjs';
@Directive({ @Directive({
selector: '[appClickedOutside]' selector: '[appClickedOutside]'
@@ -8,18 +8,18 @@ import { filter, fromEvent, Subscription } from 'rxjs';
export class ClickedOutsideDirective implements AfterViewInit, OnDestroy { export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
@Input() @Input()
ignoreElementList!: HTMLDivElement[]; ignoreElementList!: HTMLDivElement[];
@Input() @Input()
includeClickedOutside!: HTMLDivElement[]; includeClickedOutside!: HTMLDivElement[];
@Input() @Input()
clickOutsideStopWatching: boolean = false; clickOutsideStopWatching: boolean = false;
@Output() @Output()
clickOutside: EventEmitter<void> = new EventEmitter(); clickOutside: EventEmitter<void> = new EventEmitter();
clickListener!: Subscription; eventListener!: Subscription;
constructor( constructor(
private element: ElementRef, private element: ElementRef,
@@ -28,20 +28,25 @@ export class ClickedOutsideDirective implements AfterViewInit, OnDestroy {
ngAfterViewInit(): void { ngAfterViewInit(): void {
const mouseDownListener$ = fromEvent(document, 'mousedown');
const mouseUpListener$ = fromEvent(document,'mouseup');
this.eventListener = mouseUpListener$.pipe(
combineLatestWith(mouseDownListener$),
filter(([downClick, upClick]) => {
return (downClick.target as HTMLElement)
.contains(this.element.nativeElement) && (!this.isInside(
upClick.target as HTMLElement
) || this.includedList(upClick.target as HTMLElement));
})
). subscribe( () => {
!this.clickOutsideStopWatching && this.clickOutside.emit();
});
this.clickListener = fromEvent(this.document, 'click')
.pipe(
filter((event) => {
return !this.isInside(event.target as HTMLElement) || this.includedList(event.target as HTMLElement);
})
). subscribe( () => {
!this.clickOutsideStopWatching && this.clickOutside.emit();
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.clickListener?.unsubscribe(); this.eventListener?.unsubscribe();
} }
private isInside(elementToCheck: HTMLElement): boolean { private isInside(elementToCheck: HTMLElement): boolean {

View File

@@ -2,7 +2,7 @@ import { Token } from "../token/token.model";
export interface User { export interface User {
id?: number, id?: number,
fullname?: string, name?: string,
email?: string, email?: string,
username: string, username: string,
password?: string, password?: string,

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>

After

Width:  |  Height:  |  Size: 820 B

View File

@@ -0,0 +1,11 @@
<svg enable-background="new 0 0 533.5 544.3" version="1.1" viewBox="0 0 533.5 544.3" xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ns="&amp;ns_sfw;"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<path d="m533.5 278.4c0-18.5-1.5-37.1-4.7-55.3h-256.7v104.8h147c-6.1 33.8-25.7 63.7-54.4 82.7v68h87.7c51.5-47.4 81.1-117.4 81.1-200.2z" style="fill:#4285f4"/>
<path d="m272.1 544.3c73.4 0 135.3-24.1 180.4-65.7l-87.7-68c-24.4 16.6-55.9 26-92.6 26-71 0-131.2-47.9-152.8-112.3h-90.5v70.1c46.2 91.9 140.3 149.9 243.2 149.9z" style="fill:#34a853"/>
<path d="M119.3,324.3c-11.4-33.8-11.4-70.4,0-104.2V150H28.9c-38.6,76.9-38.6,167.5,0,244.4L119.3,324.3z" style="fill:#fbbc04"/>
<path d="m272.1 107.7c38.8-0.6 76.3 14 104.4 40.8l77.7-77.7c-49.2-46.2-114.5-71.6-182.1-70.8-102.9 0-197 58-243.2 150l90.4 70.1c21.5-64.5 81.8-112.4 152.8-112.4z" style="fill:#ea4335"/>
</svg>

After

Width:  |  Height:  |  Size: 1008 B