Add project files.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService',
|
||||
() => {
|
||||
let service: AuthService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AuthService);
|
||||
});
|
||||
|
||||
it('should be created',
|
||||
() => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,279 @@
|
||||
// angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpHeaders } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
// libs
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Subject } from 'rxjs';
|
||||
import { of } from 'rxjs';
|
||||
import { throwError } from 'rxjs';
|
||||
import { from } from 'rxjs';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { AuthService as Auth0Service } from '@auth0/auth0-angular';
|
||||
|
||||
import { SocialAuthService } from '@abacritt/angularx-social-login';
|
||||
import { GoogleLoginProvider } from '@abacritt/angularx-social-login';
|
||||
|
||||
// app
|
||||
import { OidcHelperService } from './oidc-helper.service';
|
||||
import { UserModel } from '../models/user.model';
|
||||
import { ApiRoutes } from '../../api-routes';
|
||||
import { RegisterModel } from '../models/register.model';
|
||||
import { LoginModel } from '../models/login.model';
|
||||
import { UserProfileModel } from '../models/user-profile.model';
|
||||
import { GeneralResultModel } from '../models/general-result.model';
|
||||
import { SubjectExtensions } from '../extensions/general.extensions';
|
||||
|
||||
export function authAppInitializerFactory(authService: AuthService): () => Promise<void> {
|
||||
if (window.location.href.indexOf('/silent-refresh.html') !== -1) {
|
||||
return () => Promise.resolve();
|
||||
}
|
||||
return () => authService.runInitialLoginSequence();
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService {
|
||||
private currentUserSubject: BehaviorSubject<UserModel>;
|
||||
currentUser$: Observable<UserModel>;
|
||||
|
||||
private isAuthenticatedSubject$ = new BehaviorSubject<boolean>(false);
|
||||
isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private oidcHelperService: OidcHelperService,
|
||||
private auth0Service: Auth0Service,
|
||||
private externalAuthService: SocialAuthService,
|
||||
) {
|
||||
this.currentUserSubject = new BehaviorSubject<UserModel>({} as UserModel);
|
||||
this.currentUser$ = this.currentUserSubject.asObservable();
|
||||
|
||||
// on external social login
|
||||
this.externalAuthService.authState.subscribe((user) => {
|
||||
oidcHelperService.loginByExternalLogin('google', user.idToken);
|
||||
});
|
||||
|
||||
this.oidcHelperService.isDoneLoading$.subscribe(isDone => {
|
||||
if (!isDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.oidcHelperService.isAuthenticated$.subscribe(authState => {
|
||||
let redirectUrl = this.getRedirect();
|
||||
if (authState.isAuthenticated) {
|
||||
this.oidcHelperService.loadUserProfile().then(e => {
|
||||
let resp = (e as any).info;
|
||||
let userProfile = {
|
||||
id: resp.id,
|
||||
email: resp.email,
|
||||
phone: resp.phone,
|
||||
firstName: resp.firstName,
|
||||
lastName: resp.lastName,
|
||||
fullName: resp.fullName,
|
||||
};
|
||||
|
||||
this.setCurrentUserValue(userProfile.id, userProfile);
|
||||
|
||||
if (authState.action === AuthenticatedActionEnum.loggedIn) {
|
||||
this.router.navigate([redirectUrl || '/dashboard/dashboard1']);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setCurrentUserValue(id: string, profile?: UserProfileModel) {
|
||||
this.currentUserSubject.next({
|
||||
id: id,
|
||||
img: 'assets/images/user/admin.jpg',
|
||||
username: profile?.email || '',
|
||||
firstName: profile?.firstName || '',
|
||||
lastName: profile?.lastName || '',
|
||||
});
|
||||
}
|
||||
|
||||
get currentUserValue(): UserModel {
|
||||
return this.currentUserSubject.value;
|
||||
}
|
||||
|
||||
login(loginModel: LoginModel): Subject<GeneralResultModel> {
|
||||
return SubjectExtensions.start<GeneralResultModel>(subject => {
|
||||
this.oidcHelperService
|
||||
.login(loginModel.username, loginModel.password)
|
||||
.subscribe(resp => {
|
||||
if (resp.access_token) {
|
||||
this.isAuthenticatedSubject$.next(true);
|
||||
}
|
||||
subject.next({ success: !!(resp.access_token) });
|
||||
},
|
||||
err => {
|
||||
subject.next({ success: false, error: err.error });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.currentUserSubject.next({} as UserModel);
|
||||
this.isAuthenticatedSubject$.next(false);
|
||||
this.oidcHelperService.logout();
|
||||
|
||||
return of({ success: false });
|
||||
}
|
||||
|
||||
register(registerModel: RegisterModel): Observable<any> {
|
||||
return this.http
|
||||
.post<RegisterModel>(ApiRoutes.UserRegister, registerModel)
|
||||
.pipe(
|
||||
map(resp => resp),
|
||||
catchError(error => {
|
||||
return throwError(error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
runInitialLoginSequence() {
|
||||
return this.oidcHelperService.runInitialLoginSequence();
|
||||
}
|
||||
|
||||
get isAuthenticated(): boolean {
|
||||
return this.oidcHelperService.hasValidAccessToken();
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return this.oidcHelperService.getAccessToken();
|
||||
}
|
||||
|
||||
loginGoogle(redirectUrl?: string): Observable<any> {
|
||||
return from(this.externalAuthService.signIn(GoogleLoginProvider.PROVIDER_ID));
|
||||
}
|
||||
|
||||
attachGoogle(): Observable<any> {
|
||||
var result = new Subject();
|
||||
|
||||
/*this.externalAuthService.signIn(GoogleLoginProvider.PROVIDER_ID).then(function (resp) {
|
||||
//console.log('attachGoogle', resp);
|
||||
//return result.next(resp);
|
||||
//this.attach(idToken.__raw, result);
|
||||
});*/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
loginAuth0(): Observable<GeneralResultModel> {
|
||||
return SubjectExtensions.start<GeneralResultModel>((subject) => {
|
||||
var subs = this.auth0Service.idTokenClaims$.subscribe(token => {
|
||||
if (token && token?.__raw) {
|
||||
subs.unsubscribe();
|
||||
|
||||
this.oidcHelperService.loginByExternalLogin('auth0', token.__raw).subscribe(login => {
|
||||
if (login.success) {
|
||||
this.isAuthenticatedSubject$.next(true);
|
||||
}
|
||||
subject.next({ success: login.success, error: login.error });
|
||||
});
|
||||
} else {
|
||||
//subject.next({ success: false, error: { description: 'Login failed' } });
|
||||
}
|
||||
});
|
||||
|
||||
this.auth0Service.getAccessTokenWithPopup().subscribe(popupToken => {
|
||||
if (!popupToken) {
|
||||
this.auth0Service.getAccessTokenSilently().subscribe(silently => {
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
attachAuth0(): Observable<any> {
|
||||
return SubjectExtensions.start<GeneralResultModel>((subject) => {
|
||||
var subs = this.auth0Service.idTokenClaims$.subscribe(token => {
|
||||
if (token && token?.__raw) {
|
||||
subs.unsubscribe();
|
||||
|
||||
this.attach(token?.__raw).subscribe(
|
||||
(resp) => subject.next({ success: true, data: token?.__raw }),
|
||||
(err) => subject.next({ success: false, data: err })
|
||||
);
|
||||
} else {
|
||||
subject.next({ success: false, data: 'Failed' });
|
||||
}
|
||||
});
|
||||
|
||||
this.auth0Service.getAccessTokenWithPopup().subscribe(popupToken => {
|
||||
if (!popupToken) {
|
||||
this.auth0Service.getAccessTokenSilently().subscribe(silently => {
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
deattach(provider: string): Observable<any> {
|
||||
var result = new Subject();
|
||||
|
||||
var headers = new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Authorization': 'Bearer ' + this.getAccessToken()
|
||||
});
|
||||
this.http
|
||||
.post<any>(ApiRoutes.UserDeattach, { provider: provider }, { headers })
|
||||
.pipe(
|
||||
map(resp => resp),
|
||||
catchError(error => {
|
||||
return throwError(error);
|
||||
}))
|
||||
.subscribe((resp) => result.next(resp));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private attach(token: string): Observable<any> {
|
||||
var data = { provider: 'auth0', token: token };
|
||||
var headers = new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json, text/plain, */*',
|
||||
'Authorization': 'Bearer ' + this.getAccessToken()
|
||||
});
|
||||
return this.http
|
||||
.post<any>(ApiRoutes.UserAttach, data, { headers });
|
||||
}
|
||||
|
||||
private getRedirect(): string | undefined {
|
||||
var redirectUrl = this.route.snapshot.queryParams['r'];
|
||||
redirectUrl = redirectUrl ? decodeURIComponent(redirectUrl) : undefined;
|
||||
return redirectUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export enum AuthenticatedActionEnum {
|
||||
init,
|
||||
update,
|
||||
loggedIn,
|
||||
loggedOff,
|
||||
}
|
||||
|
||||
export interface IAuthenticatedState {
|
||||
isAuthenticated: boolean,
|
||||
action: AuthenticatedActionEnum,
|
||||
redirectUrl?: string,
|
||||
}
|
||||
|
||||
export enum StateEnum {
|
||||
undefined,
|
||||
inited,
|
||||
completed,
|
||||
failed,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class DirectionService {
|
||||
private data = new BehaviorSubject('');
|
||||
currentData = this.data.asObservable();
|
||||
|
||||
constructor() {
|
||||
//constructor
|
||||
}
|
||||
|
||||
updateDirection(item: string) {
|
||||
this.data.next(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LanguageService {
|
||||
languages: string[] = ['en', 'es', 'de'];
|
||||
|
||||
constructor(public translate: TranslateService) {
|
||||
let browserLang: string;
|
||||
translate.addLangs(this.languages);
|
||||
|
||||
if (localStorage.getItem('lang')) {
|
||||
browserLang = localStorage.getItem('lang') as string;
|
||||
} else {
|
||||
browserLang = translate.getBrowserLang() as string;
|
||||
}
|
||||
translate.use(browserLang.match(/en|es|de/) ? browserLang : 'en');
|
||||
}
|
||||
|
||||
setLanguage(lang: string) {
|
||||
this.translate.use(lang);
|
||||
localStorage.setItem('lang', lang);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
// angular
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { OAuthService } from 'angular-oauth2-oidc';
|
||||
import { OAuthErrorEvent } from 'angular-oauth2-oidc';
|
||||
|
||||
// libs
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
import { Subject, Observable, BehaviorSubject, from } from 'rxjs';
|
||||
|
||||
// app
|
||||
import { GeneralResultModel } from '../models/general-result.model';
|
||||
|
||||
@Injectable()
|
||||
export class OidcHelperService {
|
||||
private isAuthenticatedSubject$ = new BehaviorSubject<IAuthenticatedState>({
|
||||
isAuthenticated: false,
|
||||
action: AuthenticatedActionEnum.init
|
||||
});
|
||||
isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();
|
||||
|
||||
private isDoneLoadingFailed = false;
|
||||
private isDoneLoadingSubject$ = new BehaviorSubject<boolean>(false);
|
||||
isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();
|
||||
|
||||
constructor(
|
||||
private oauthService: OAuthService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.oauthService.oidc = false;
|
||||
|
||||
// all events handler
|
||||
this.oauthService.events.subscribe(event => {
|
||||
if (event instanceof OAuthErrorEvent) {
|
||||
console.error('OAuthErrorEvent Object:', event);
|
||||
if (event.type === 'discovery_document_validation_error') {
|
||||
this.isDoneLoadingFailed = true;
|
||||
this.setIsAuthenticatedSubject$(false, AuthenticatedActionEnum.update);
|
||||
}
|
||||
} else {
|
||||
var hasValidAccessToken = this.hasValidAccessToken();
|
||||
var isAuthenticated = this.isAuthenticatedSubject$.getValue().isAuthenticated;
|
||||
if (isAuthenticated !== hasValidAccessToken) {
|
||||
this.setIsAuthenticatedSubject$(hasValidAccessToken, AuthenticatedActionEnum.update);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This is tricky, as it might cause race conditions (where access_token is set in another
|
||||
// tab before everything is said and done there.
|
||||
// TODO: Improve this setup. See: https://github.com/jeroenheijmans/sample-angular-oauth2-oidc-with-auth-guards/issues/2
|
||||
window.addEventListener('storage',
|
||||
(event) => {
|
||||
// The `key` is `null` if the event was caused by `.clear()`
|
||||
if (event.key !== 'access_token' && event.key !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
'Noticed changes to access_token (most likely from another tab), updating isAuthenticated');
|
||||
this.setIsAuthenticatedSubject$(this.hasValidAccessToken(), AuthenticatedActionEnum.update);
|
||||
|
||||
if (!this.hasValidAccessToken()) {
|
||||
console.log('storage access_token updated !this.hasValidAccessToken()');
|
||||
this.logout();
|
||||
this.navigateToLoginPage();
|
||||
}
|
||||
});
|
||||
|
||||
// init login stata
|
||||
this.setIsAuthenticatedSubject$(this.hasValidAccessToken(), AuthenticatedActionEnum.update);
|
||||
|
||||
// TODO: some time receive 'message' with data 'error' == 'session_error''
|
||||
/*window.addEventListener('message', e => {
|
||||
console.log('message', e);;
|
||||
if (e.origin === 'https://dev-w08xm0pi.us.auth0.com') {
|
||||
console.log('message stopPropagation');;
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
this.oauthService.events.subscribe(x => {
|
||||
console.log('events', x);
|
||||
});*/
|
||||
this.oauthService.events
|
||||
.pipe(filter(e => ['session_terminated', 'session_error'].includes(e.type)))
|
||||
.subscribe(e => {
|
||||
console.log('events session_terminated', e, this.hasValidAccessToken());
|
||||
this.navigateToLoginPage();
|
||||
});
|
||||
|
||||
this.oauthService.setupAutomaticSilentRefresh();
|
||||
}
|
||||
|
||||
private setIsAuthenticatedSubject$(
|
||||
isAuthenticated: boolean,
|
||||
action: AuthenticatedActionEnum
|
||||
) {
|
||||
var state = this.isAuthenticatedSubject$.getValue();
|
||||
if (state.isAuthenticated !== isAuthenticated || state.action !== action
|
||||
) {
|
||||
this.isAuthenticatedSubject$.next({ isAuthenticated, action });
|
||||
}
|
||||
}
|
||||
|
||||
private navigateToLoginPage() {
|
||||
this.router.navigateByUrl('/authentication/signin');
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.oauthService.logOut(true);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.oauthService.silentRefresh();
|
||||
}
|
||||
|
||||
hasValidAccessToken() {
|
||||
return this.oauthService.hasValidAccessToken();
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return this.oauthService.getAccessToken();
|
||||
}
|
||||
|
||||
login(username: string, password: string) {
|
||||
//return this.oauthService.fetchTokenUsingPasswordFlow(username, password);
|
||||
return from(this.oauthService.fetchTokenUsingPasswordFlow(username, password));
|
||||
}
|
||||
|
||||
loginByExternalLogin(
|
||||
provider: string,
|
||||
token: string,
|
||||
redirectUrl?: string
|
||||
): Observable<GeneralResultModel> {
|
||||
var result = new Subject<GeneralResultModel>();
|
||||
|
||||
let params = {
|
||||
token: token,
|
||||
provider: provider,
|
||||
};
|
||||
this.oauthService
|
||||
.fetchTokenUsingGrant('external', params)
|
||||
.then(x => {
|
||||
this.setIsAuthenticatedSubject$(this.hasValidAccessToken(), AuthenticatedActionEnum.loggedIn);
|
||||
result.next({ success: true });
|
||||
})
|
||||
.catch(err => {
|
||||
result.next({ success: false, error: { code: err, description: err } });
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
loadUserProfile(): Promise<object> {
|
||||
return this.oauthService.loadUserProfile();
|
||||
}
|
||||
|
||||
runInitialLoginSequence(): Promise<void> {
|
||||
if (location.hash) {
|
||||
//console.log('Encountered hash fragment, plotting as table...');
|
||||
//console.table(location.hash.substr(1).split('&').map(kvp => kvp.split('=')));
|
||||
}
|
||||
if (this.isDoneLoadingFailed) {
|
||||
return new Promise<void>(resolve => setTimeout(() => resolve(), 1500));
|
||||
}
|
||||
// 0. LOAD CONFIG:
|
||||
// First we have to check to see how the IdServer is
|
||||
// currently configured:
|
||||
return this.oauthService.loadDiscoveryDocument()
|
||||
|
||||
// For demo purposes, we pretend the previous call was very slow
|
||||
.then(() => {
|
||||
new Promise<void>(resolve => setTimeout(() => resolve(), 1500));
|
||||
})
|
||||
|
||||
// 1. HASH LOGIN:
|
||||
// Try to log in via hash fragment after redirect back
|
||||
// from IdServer from initImplicitFlow:
|
||||
.then(() => {
|
||||
this.oauthService.tryLogin();
|
||||
})
|
||||
.then(() => {
|
||||
if (this.hasValidAccessToken()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// 2. SILENT LOGIN:
|
||||
// Try to log in via a refresh because then we can prevent
|
||||
// needing to redirect the user:
|
||||
return this.oauthService.silentRefresh()
|
||||
.then(() => Promise.resolve())
|
||||
.catch(result => {
|
||||
// Subset of situations from https://openid.net/specs/openid-connect-core-1_0.html#AuthError
|
||||
// Only the ones where it's reasonably sure that sending the
|
||||
// user to the IdServer will help.
|
||||
const errorResponsesRequiringUserInteraction = [
|
||||
'interaction_required',
|
||||
'login_required',
|
||||
'account_selection_required',
|
||||
'consent_required',
|
||||
];
|
||||
|
||||
if (result &&
|
||||
result.reason &&
|
||||
errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {
|
||||
|
||||
// 3. ASK FOR LOGIN:
|
||||
// At this point we know for sure that we have to ask the
|
||||
// user to log in, so we redirect them to the IdServer to
|
||||
// enter credentials.
|
||||
//
|
||||
// Enable this to ALWAYS force a user to login.
|
||||
// this.login();
|
||||
//
|
||||
// Instead, we'll now do this:
|
||||
console.warn(
|
||||
'User interaction is needed to log in, we will wait for the user to manually log in.');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// We can't handle the truth, just pass on the problem to the
|
||||
// next handler.
|
||||
return Promise.reject(result);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
this.isDoneLoadingSubject$.next(true);
|
||||
|
||||
// Check for the strings 'undefined' and 'null' just to be sure. Our current
|
||||
// login(...) should never have this, but in case someone ever calls
|
||||
// initImplicitFlow(undefined | null) this could happen.
|
||||
if (this.oauthService.state &&
|
||||
this.oauthService.state !== 'undefined' &&
|
||||
this.oauthService.state !== 'null') {
|
||||
let stateUrl = this.oauthService.state;
|
||||
if (stateUrl.startsWith('/') === false) {
|
||||
stateUrl = decodeURIComponent(stateUrl);
|
||||
}
|
||||
console.log(`There was state of ${this.oauthService.state}, so we are sending you to: ${stateUrl}`);
|
||||
this.router.navigateByUrl(stateUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => this.isDoneLoadingSubject$.next(true));
|
||||
}
|
||||
}
|
||||
|
||||
export enum AuthenticatedActionEnum {
|
||||
init,
|
||||
update,
|
||||
loggedIn,
|
||||
loggedOff,
|
||||
}
|
||||
|
||||
export interface IAuthenticatedState {
|
||||
isAuthenticated: boolean,
|
||||
action: AuthenticatedActionEnum,
|
||||
redirectUrl?: string,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class RightSidebarService {
|
||||
private sidebarSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
sidebarState = this.sidebarSubject.asObservable();
|
||||
|
||||
setRightSidebar = (value: boolean) => {
|
||||
this.sidebarSubject.next(value);
|
||||
};
|
||||
|
||||
constructor() {
|
||||
//constructor
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user