diff --git a/nae.json b/nae.json index 2e5b56c53..93800125a 100644 --- a/nae.json +++ b/nae.json @@ -4,7 +4,7 @@ "providers": { "auth": { "address": "http://localhost:8080/api/", - "authentication": "Basic", + "authentication": "BasicWithRealm", "endpoints": { "login": "auth/login", "logout": "auth/logout", @@ -14,12 +14,19 @@ "verify": "auth/token/verify", "invite": "auth/invite", "reset": "auth/reset", - "recover": "/auth/recover" + "recover": "auth/recover" }, "sessionBearer": "X-Auth-Token", "sessionTimeoutEnabled": false, "sessionTimeout": 900, - "jwtBearer": "X-Jwt-Token" + "jwtBearer": "X-Jwt-Token", + "sso": { + "enable": true, + "clientId": "dev-cluster-worker", + "redirectUrl": "https://iam.mudu.dev.netgrif.cloud/realms/netgrif-cloud-testing/protocol/openid-connect/auth", + "refreshUrl": "http://localhost:8800/api/auth/login", + "scopes": ["openid","email","profile","roles"] + } }, "resources": [ { diff --git a/projects/netgrif-components-core/src/assets/i18n/de.json b/projects/netgrif-components-core/src/assets/i18n/de.json index a8f7aa7ba..4df093efa 100644 --- a/projects/netgrif-components-core/src/assets/i18n/de.json +++ b/projects/netgrif-components-core/src/assets/i18n/de.json @@ -311,6 +311,7 @@ "login": "Benutzername", "wrongCredentials": "Falsche Anmeldeinformationen!", "loginButton": "Anmelden", + "ssoButton": "SSO-Anmeldung", "reset": "Kennwort wiederherstellen", "sign": "Registrieren", "enterPass": "Ihre Kennwort eingeben" diff --git a/projects/netgrif-components-core/src/assets/i18n/en.json b/projects/netgrif-components-core/src/assets/i18n/en.json index d2bbf05e8..3a24d3e87 100644 --- a/projects/netgrif-components-core/src/assets/i18n/en.json +++ b/projects/netgrif-components-core/src/assets/i18n/en.json @@ -311,6 +311,7 @@ "login": "Username", "wrongCredentials": "Wrong credentials!", "loginButton": "Log in", + "ssoButton": "Log with SSO", "reset": "Reset password", "sign": "Sign Up", "enterPass": "Enter your password" diff --git a/projects/netgrif-components-core/src/assets/i18n/sk.json b/projects/netgrif-components-core/src/assets/i18n/sk.json index e47da7ff9..15d2ca7c6 100644 --- a/projects/netgrif-components-core/src/assets/i18n/sk.json +++ b/projects/netgrif-components-core/src/assets/i18n/sk.json @@ -311,6 +311,7 @@ "login": "Prihlasovacie meno", "wrongCredentials": "Nesprávne prihlasovacie údaje!", "loginButton": "Prihlásiť", + "ssoButton": "Prihlásiť sa pomocou SSO", "reset": "Obnova hesla", "sign": "Registrovať", "enterPass": "Zadajte svoje heslo" diff --git a/projects/netgrif-components-core/src/commons/schema.ts b/projects/netgrif-components-core/src/commons/schema.ts index ae68c47ac..d02835a32 100644 --- a/projects/netgrif-components-core/src/commons/schema.ts +++ b/projects/netgrif-components-core/src/commons/schema.ts @@ -46,10 +46,19 @@ export interface Auth { authentication: string; sessionBearer?: string; endpoints?: string | { [k: string]: string }; + sso?: Sso; [k: string]: any; } +export interface Sso { + enable: boolean; + redirectUrl: string; + refreshUrl: string; + clientId: string; + scopes: Array; +} + export interface Resource { name: string; address: string; diff --git a/projects/netgrif-components-core/src/lib/forms/login/abstract-login-form.component.ts b/projects/netgrif-components-core/src/lib/forms/login/abstract-login-form.component.ts index 448fe9516..37579c281 100644 --- a/projects/netgrif-components-core/src/lib/forms/login/abstract-login-form.component.ts +++ b/projects/netgrif-components-core/src/lib/forms/login/abstract-login-form.component.ts @@ -5,6 +5,8 @@ import {UserService} from '../../user/services/user.service'; import {User} from '../../user/models/user'; import {LoadingEmitter} from '../../utility/loading-emitter'; import {take} from 'rxjs/operators'; +import {ConfigurationService} from "../../configuration/configuration.service"; +import {Sso} from "../../../commons/schema"; @Component({ selector: 'ncc-abstract-login-field', @@ -15,6 +17,7 @@ export abstract class AbstractLoginFormComponent implements HasForm, OnDestroy { public rootFormGroup: FormGroup; public hidePassword = true; public loading: LoadingEmitter; + protected showSsoButton: boolean; @Input() public showSignUpButton: boolean; @Input() public showForgottenPasswordButton: boolean; @@ -23,7 +26,7 @@ export abstract class AbstractLoginFormComponent implements HasForm, OnDestroy { @Output() public signUp: EventEmitter; @Output() public formSubmit: EventEmitter; - protected constructor(formBuilder: FormBuilder, protected _userService: UserService) { + protected constructor(formBuilder: FormBuilder, protected _userService: UserService, protected _config: ConfigurationService) { this.rootFormGroup = formBuilder.group({ login: [''], password: [''] @@ -33,6 +36,8 @@ export abstract class AbstractLoginFormComponent implements HasForm, OnDestroy { this.signUp = new EventEmitter(); this.formSubmit = new EventEmitter(); this.loading = new LoadingEmitter(); + let ssoConfig: Sso = this._config.getConfigurationSubtree(['providers', 'auth', 'sso']) + this.showSsoButton = ssoConfig?.enable } ngOnDestroy(): void { diff --git a/projects/netgrif-components-core/src/lib/forms/login/login-sso/abstract-login-sso.component.spec.ts b/projects/netgrif-components-core/src/lib/forms/login/login-sso/abstract-login-sso.component.spec.ts new file mode 100644 index 000000000..e73bf2b76 --- /dev/null +++ b/projects/netgrif-components-core/src/lib/forms/login/login-sso/abstract-login-sso.component.spec.ts @@ -0,0 +1 @@ +// todo 2118 diff --git a/projects/netgrif-components-core/src/lib/forms/login/login-sso/abstract-login-sso.component.ts b/projects/netgrif-components-core/src/lib/forms/login/login-sso/abstract-login-sso.component.ts new file mode 100644 index 000000000..2b71c0a0f --- /dev/null +++ b/projects/netgrif-components-core/src/lib/forms/login/login-sso/abstract-login-sso.component.ts @@ -0,0 +1,108 @@ +import {Component, OnDestroy} from "@angular/core"; +import {ActivatedRoute, Params, Router} from "@angular/router"; +import {Observable, throwError} from "rxjs"; +import {catchError} from "rxjs/operators"; +import {HttpClient} from "@angular/common/http"; +import {ConfigurationService} from "../../../configuration/configuration.service"; +import {LoggerService} from '../../../logger/services/logger.service'; +import {LoadingEmitter} from '../../../utility/loading-emitter'; +import {SnackBarService} from "../../../snack-bar/services/snack-bar.service"; +import {Sso} from "../../../../commons/schema"; +import {TranslateService} from "@ngx-translate/core"; + + +@Component({ + selector: 'ncc-abstract-login-field', + template: '' +}) +export abstract class AbstractLoginSsoComponent implements OnDestroy { + + private _ssoConfig: Sso; + protected loading: LoadingEmitter; + + protected constructor( + protected _config: ConfigurationService, + protected _http: HttpClient, + protected _snackbar: SnackBarService, + protected _log: LoggerService, + protected _router: Router, + protected _activeRouter: ActivatedRoute, + protected _translate: TranslateService + ) { + this._ssoConfig = this._config.getConfigurationSubtree(['providers', 'auth', 'sso']); + this.loading = new LoadingEmitter(); + this._activeRouter.queryParams.subscribe((params) => { + if (!!params.code) { + this.loginFromCode(params); + } + }); + } + + ngOnDestroy(): void { + this.loading.complete(); + } + + public redirectToSso(): void { + let redirectUrl: string = this.getRedirectUrl(); + this._log.info("Redirecting to " + redirectUrl) + window.location.href = redirectUrl; + } + + public loginFromCode(params: Params) { + if (!params.code) { + return; + } + + this.loading.on(); + this._log.debug('Handling access token: ' + params.code) + const token$ = this.getToken({ + grantType: 'authorization_code', + code: params.code, + realmId: '', // todo send realm id + redirectUri: location.origin + '/' + this._config.getConfigurationSubtree(['services', 'auth', 'toLoginRedirect']), + }); + token$.subscribe( + token => { + this.loading.off(); + if (!!token) { + this.redirectToHome(); + } + }, + ); + } + + private getRedirectUrl(): string { + const myQuery = this._ssoConfig.redirectUrl + '?'; + const options: { [index: string]: string } = { + client_id: this._ssoConfig.clientId, + redirect_uri: location.origin + '/' + this._config.getConfigurationSubtree(['services', 'auth', 'toLoginRedirect']), + response_type: 'code', + scope: this._ssoConfig.scopes.join(' '), + }; + return myQuery + Object.keys(options).map( + key => { + return encodeURIComponent(key) + '=' + encodeURIComponent(options[key]); + }, + ).join('&'); + } + + private getToken(body: any): Observable { + const url = this._ssoConfig.refreshUrl; + if (!url) { + return throwError(() => new Error('Refresh URL is not defined in the config [nae.providers.auth.sso.refreshUrl]')); + } + return this._http.post(url, body, + {headers: {'Content-Type': 'application/json'}}).pipe( + catchError(error => { + this.loading.off(); + this._snackbar.openErrorSnackBar(this._translate.instant('forms.login.wrongCredentials')); + return throwError(() => error); + }), + ); + } + + private redirectToHome() { + this._router.navigate(['/' + this._config.getConfigurationSubtree(['services', 'auth', 'onLoginRedirect'])]) + .then((value) => { this._log.debug('Routed to ' + value); }); + } +} diff --git a/projects/netgrif-components-core/src/lib/forms/public-api.ts b/projects/netgrif-components-core/src/lib/forms/public-api.ts index 1d96fe436..ee45a0c70 100644 --- a/projects/netgrif-components-core/src/lib/forms/public-api.ts +++ b/projects/netgrif-components-core/src/lib/forms/public-api.ts @@ -1,5 +1,6 @@ export * from './email-submission/abstract-email-submission-form.component'; export * from './login/abstract-login-form.component'; +export * from './login/login-sso/abstract-login-sso.component'; export * from './registration/abstract-registration-form.component'; export * from './forgotten-password/abstract-forgotten-password.component'; export * from './models/abstract-registration.component'; diff --git a/projects/netgrif-components/src/lib/forms/login/login-form.component.html b/projects/netgrif-components/src/lib/forms/login/login-form.component.html index 273786640..a0182bd07 100644 --- a/projects/netgrif-components/src/lib/forms/login/login-form.component.html +++ b/projects/netgrif-components/src/lib/forms/login/login-form.component.html @@ -40,6 +40,9 @@ {{ 'forms.login.loginButton' | translate}} +
+ +
diff --git a/projects/netgrif-components/src/lib/forms/login/login-form.component.ts b/projects/netgrif-components/src/lib/forms/login/login-form.component.ts index 67d8839c7..792a898d6 100644 --- a/projects/netgrif-components/src/lib/forms/login/login-form.component.ts +++ b/projects/netgrif-components/src/lib/forms/login/login-form.component.ts @@ -1,6 +1,6 @@ import {Component} from '@angular/core'; import {FormBuilder} from '@angular/forms'; -import {AbstractLoginFormComponent, UserService} from '@netgrif/components-core'; +import {AbstractLoginFormComponent, ConfigurationService, UserService} from '@netgrif/components-core'; @Component({ selector: 'nc-login-form', @@ -8,7 +8,7 @@ import {AbstractLoginFormComponent, UserService} from '@netgrif/components-core' styleUrls: ['./login-form.component.scss'] }) export class LoginFormComponent extends AbstractLoginFormComponent { - constructor(formBuilder: FormBuilder, protected _userService: UserService) { - super(formBuilder, _userService); + constructor(formBuilder: FormBuilder, protected _userService: UserService, protected _config: ConfigurationService) { + super(formBuilder, _userService, _config); } } diff --git a/projects/netgrif-components/src/lib/forms/login/login-form.module.ts b/projects/netgrif-components/src/lib/forms/login/login-form.module.ts index fde185420..2466b0493 100644 --- a/projects/netgrif-components/src/lib/forms/login/login-form.module.ts +++ b/projects/netgrif-components/src/lib/forms/login/login-form.module.ts @@ -4,11 +4,12 @@ import {FlexLayoutModule} from '@ngbracket/ngx-layout'; import {ReactiveFormsModule} from '@angular/forms'; import {LoginFormComponent} from './login-form.component'; import {MaterialModule, TranslateLibModule} from '@netgrif/components-core'; +import {LoginSsoComponent} from "./login-sso/login-sso.component"; @NgModule({ - declarations: [LoginFormComponent], - exports: [LoginFormComponent], + declarations: [LoginFormComponent, LoginSsoComponent], + exports: [LoginFormComponent, LoginSsoComponent], imports: [ CommonModule, MaterialModule, diff --git a/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.html b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.html new file mode 100644 index 000000000..23934517d --- /dev/null +++ b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.html @@ -0,0 +1,9 @@ + diff --git a/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.scss b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.spec.ts b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.spec.ts new file mode 100644 index 000000000..37d77c8da --- /dev/null +++ b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginSsoComponent } from './login-sso.component'; + +// todo 2118 + +xdescribe('LoginSsoComponent', () => { + let component: LoginSsoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LoginSsoComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginSsoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.ts b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.ts new file mode 100644 index 000000000..ad0337e28 --- /dev/null +++ b/projects/netgrif-components/src/lib/forms/login/login-sso/login-sso.component.ts @@ -0,0 +1,24 @@ +import {HttpClient} from '@angular/common/http'; +import {Component} from '@angular/core'; +import {ActivatedRoute, Router} from '@angular/router'; +import {AbstractLoginSsoComponent, ConfigurationService, LoggerService, SnackBarService} from '@netgrif/components-core'; +import {TranslateService} from "@ngx-translate/core"; + +@Component({ + selector: 'nc-login-sso', + templateUrl: './login-sso.component.html', + styleUrls: ['./login-sso.component.scss'], +}) +export class LoginSsoComponent extends AbstractLoginSsoComponent { + constructor( + protected _config: ConfigurationService, + protected _http: HttpClient, + protected _snackbar: SnackBarService, + protected _log: LoggerService, + protected _router: Router, + protected _activeRouter: ActivatedRoute, + protected _translate: TranslateService + ) { + super(_config, _http, _snackbar, _log, _router, _activeRouter, _translate); + } +} diff --git a/projects/netgrif-components/src/lib/forms/public-api.ts b/projects/netgrif-components/src/lib/forms/public-api.ts index 694bc6b23..ec27a47c2 100644 --- a/projects/netgrif-components/src/lib/forms/public-api.ts +++ b/projects/netgrif-components/src/lib/forms/public-api.ts @@ -1,6 +1,7 @@ /* COMPONENTS */ export * from './email-submission/email-submission-form.component'; export * from './login/login-form.component'; +export * from './login/login-sso/login-sso.component'; export * from './registration/registration-form.component'; export * from './forgotten-password/forgotten-password-form.component'; export * from './change-password/change-password-form.component';