UNPKG

@ngx-addons/omni-auth-core

Version:

Core library for authentication in Angular applications.

392 lines (373 loc) 14.3 kB
import * as i0 from '@angular/core'; import { InjectionToken, inject, signal, Injectable, computed, Pipe } from '@angular/core'; import { filter, take, switchMap, skipWhile, map } from 'rxjs'; import { createUrlTreeFromSnapshot, Router } from '@angular/router'; import { toObservable } from '@angular/core/rxjs-interop'; class JwtToken { token; payload; expireAt; constructor(token, payload) { this.token = token; this.payload = payload; this.expireAt = payload.exp ? new Date(payload.exp * 1000) : null; } isExpired() { if (!this.payload.exp) { return false; } const now = Math.floor(Date.now() / 1000); return this.payload.exp < now; } isValid() { return !this.isExpired(); } toString() { return this.token; } } class TokenProxy { fetcher; constructor(fetcher) { this.fetcher = fetcher; } async getIdToken() { const tokens = await this.fetcher(false); const idToken = tokens?.idToken; if (!idToken) { return null; } if (idToken.isValid()) { return idToken; } const freshTokens = await this.fetcher(true); const freshIdToken = freshTokens?.idToken; if (!freshIdToken) { return null; } return freshIdToken; } async getAccessToken() { const tokens = await this.fetcher(false); const accessToken = tokens?.accessToken; if (!accessToken) { return null; } if (accessToken.isValid()) { return accessToken; } const freshTokens = await this.fetcher(true); const freshAccessToken = freshTokens?.accessToken; if (!freshAccessToken) { return null; } return freshAccessToken; } } class OmniAuthError { error; constructor(error) { this.error = error; } getErrorMessage() { return this.error instanceof Error ? this.error.message : 'Unknown error'; } } const isError = (response) => { return response instanceof OmniAuthError; }; class OmniAuthService { } const AUTH_CONFIG = new InjectionToken('AUTH_CONFIG'); const configureAuth = (params) => { return [ { provide: OmniAuthService, useClass: params.authService, }, { provide: AUTH_CONFIG, useValue: params, }, ]; }; const jwtInterceptor = (request, next) => { const config = inject(AUTH_CONFIG); const authService = inject(OmniAuthService); const bearerConfig = config.bearerAuthentication; if (bearerConfig?.whitelistedEndpoints?.length) { const whiteListed = bearerConfig?.whitelistedEndpoints.find((url) => { if (url instanceof RegExp) { return url.test(request.url); } return request.url.includes(url); }); if (!whiteListed) { return next(request.clone()); } } return authService.accessToken$.pipe( // wait until the token is loaded // even if it's null, session is checked, but a user is not authenticated filter((token) => token !== undefined), take(1), switchMap((token) => { if (!token) { return next(request.clone()); } let headers = request.headers; headers = headers.append(bearerConfig?.headerName ?? 'Authorization', `${bearerConfig?.headerValuePrefix ?? 'Bearer '}${token}`); const newReq = request.clone({ headers }); return next(newReq); })); }; const toTitleCase = (phrase) => { return phrase .toLowerCase() .split(' ') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }; const emailToFullName = (email) => { if (!email) { return null; } const emailParts = email.split(/[@+]/g); if (emailParts.length < 2) { return null; } const result = emailParts[0].replace(/[.,]/g, ' '); if (!result) { return null; } return toTitleCase(result); }; class ActionErrorCollectorService { #currentError = signal(null, ...(ngDevMode ? [{ debugName: "#currentError" }] : [])); currentError = this.#currentError; reset() { this.#currentError.set(null); } handle(error) { this.#currentError.set(error); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ActionErrorCollectorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ActionErrorCollectorService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ActionErrorCollectorService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class ErrorMessagePipe { #collector = inject(ActionErrorCollectorService); transform(source, messages) { return computed(() => { const error = this.#collector.currentError(); if (!error) { return null; } if (error.source !== source) { return null; } if (error.silent) { return null; } return messages[error.code] ?? messages.unknown; }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ErrorMessagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.1.4", ngImport: i0, type: ErrorMessagePipe, isStandalone: true, name: "errorMessage" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ErrorMessagePipe, decorators: [{ type: Pipe, args: [{ name: 'errorMessage', standalone: true }] }] }); class RuntimeError extends OmniAuthError { error; possibleSolution; constructor(error, possibleSolution) { super(error); this.error = error; this.possibleSolution = possibleSolution; } } class FlowError extends OmniAuthError { source; code; error; silent; constructor(source, code, error, silent = false) { super(error); this.source = source; this.code = code; this.error = error; this.silent = silent; } } /** * Defines common patterns used for validation in the OmniAuth Core library. * * These patterns are used for validating passwords and email addresses. */ const passwordPattern = /^(?!\s+)(?!.*\s+$)(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[$^*.[\]{}()?"!@#%&/\\,><':;|_~`=+\- ])[A-Za-z0-9$^*.[\]{}()?"!@#%&/\\,><':;|_~`=+\- ]{8,256}$/; const emailPattern = /^(([^<>()[\]\\.,;:\s@]+(\.[^<>()[\]\\.,;:\s@]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; var patterns$1 = /*#__PURE__*/Object.freeze({ __proto__: null, emailPattern: emailPattern, passwordPattern: passwordPattern }); const defaultContent = { loggedIn: { welcomeMessage: 'Welcome back, {{displayName}}!', welcomeMessageNoDisplayName: 'Welcome back!', }, common: { emailLabel: 'Email', passwordLabel: 'Password', fullNameLabel: 'Full name', fullNameErrorRequiredText: 'Full name is required', fullNameErrorMinLengthText: 'Full name needs to be at least 2 characters long', fullNameErrorMaxLengthText: 'Full name can be maximum 255 characters long', fullNamePlaceholder: 'Joe Doe', emailErrorRequiredText: 'Email is required', emailErrorPatternText: 'Email is invalid', emailErrorMinLengthText: 'Email needs to be at least 3 characters long', emailErrorMaxLengthText: 'Email can be maximum 255 characters long', emailPlaceholder: 'joe.doe@example.com', passwordErrorRequiredText: 'Password is required', passwordErrorMinLengthText: 'Password needs to be at least 8 characters long', passwordPatternText: 'Password must contain at least 8 characters, one uppercase letter,' + ' one lowercase letter, one number and one special character', passwordPlaceholder: '********', codeLabel: 'Code', codeErrorRequiredText: 'Code is required', codePlaceholder: '000000', backToSignInLabel: 'Back to login', icons: { back: 'chevron_backward', email: 'email', }, }, signIn: { title: 'Login', errorSubmitMessage: 'Error while signing in', submitLabel: 'Login', forgetPassword: 'I forgot my password', }, signUp: { title: 'Register', errorSubmitMessage: 'Error while signing up', submitLabel: 'Create account', termsAndConditionsText: 'By continuing, you agree to our', termsAndConditionsLinkText: 'Terms and Conditions', }, confirmationSignUp: { subTitle: 'Confirm your e-mail address', paragraph: 'A confirmation e-mail has been sent.\n' + 'Check your inbox and write the code below to confirm your e-mail address.', errorSubmitMessage: 'Error while signing up', submitLabel: 'Verify', resendLabel: 'Resend code', errorResendMessage: 'Error while resending code', }, resetPassword: { title: 'Confirmation code', sendCodeMessage: 'Enter the e-mail address you used to sign up, we will send you a code to reset your password.', providePasswordMessage: 'Enter code and new password', repeatPassword: 'Repeat password', errorSubmitMessage: 'Error while resetting password', errorSendCodeMessage: 'Error while sending code', sendCodeLabel: 'Send code', submitLabel: 'Reset password', }, socialButtons: { orLine: 'or use one of these options', signInWithGoogle: 'Sign in with Google', signInWithApple: 'Sign in with Apple', signInWithFacebook: 'Sign in with Facebook', }, errors: { invalidCode: 'Provided code is invalid', usernameNotFound: 'Username not found', incorrectUsernameOrPassword: 'Password or email is incorrect', userDoesNotExist: 'User does not exist', userIsNotConfirmed: 'User is not confirmed', userAlreadyExists: 'User already exists', notVerified: 'Check your email to verify your account', alreadySignedIn: 'You are already signed in', signInWithRedirectFailure: 'Sign in using external provider failed', invalidConfiguration: 'Missing or invalid configuration, see console for details', cancelledFlow: 'Flow was cancelled by user', unknown: 'An unknown error occurred', }, }; /** * @description Guard that checks if the user is authenticated user otherwise redirects to the specified route. * @param config */ const onlyAuthenticated = (config) => (route) => { const authService = inject(OmniAuthService); const libConfig = inject(AUTH_CONFIG); const loading$ = toObservable(authService.authState.isLoading); return loading$.pipe(skipWhile((loading) => loading), map(() => { const authenticated = authService.authState.value().state === 'authenticated'; if (!authenticated) { return createUrlTreeFromSnapshot(route, config?.redirectTo ?? libConfig.routing?.guest ?? ['/']); } return true; }), take(1)); }; /** * @description Guard that checks if the user is not authenticated otherwise redirects to the specified route. * @param config */ const onlyGuest = (config) => { return (route) => { const authService = inject(OmniAuthService); const libConfig = inject(AUTH_CONFIG); const loading$ = toObservable(authService.authState.isLoading); return loading$.pipe(skipWhile((loading) => loading), map(() => { const authenticated = authService.authState.value().state === 'authenticated'; if (authenticated) { return createUrlTreeFromSnapshot(route, config?.redirectTo ?? libConfig.routing?.secured ?? ['/']); } return true; }), take(1)); }; }; class AuthRouteService { #router = inject(Router); #env = inject(AUTH_CONFIG); currentStep = signal('login', ...(ngDevMode ? [{ debugName: "currentStep" }] : [])); currentEmail = signal(null, ...(ngDevMode ? [{ debugName: "currentEmail" }] : [])); nextStep(state, details = {}) { this.currentStep.set(state); this.currentEmail.set(details.email ?? null); } navigateToGuestPage(rememberPage = false) { // todo - implement rememberPage logic if (!this.#env.routing?.guest) { return; } return this.#router.navigate(this.#env.routing.guest); } navigateToSecuredPage() { if (!this.#env.routing?.secured) { return; } return this.#router.navigate(this.#env.routing.secured); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthRouteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthRouteService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthRouteService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /** * Generated bundle index. Do not edit. */ export { AUTH_CONFIG, ActionErrorCollectorService, AuthRouteService, ErrorMessagePipe, FlowError, JwtToken, OmniAuthError, OmniAuthService, RuntimeError, TokenProxy, configureAuth, defaultContent, emailToFullName, isError, jwtInterceptor, onlyAuthenticated, onlyGuest, patterns$1 as patterns }; //# sourceMappingURL=ngx-addons-omni-auth-core.mjs.map