UNPKG

@ngx-addons/omni-auth-core

Version:

Core library for authentication in Angular applications.

419 lines (400 loc) 15.6 kB
import * as i0 from '@angular/core'; import { inject, signal, Injectable, InjectionToken, computed, Pipe } from '@angular/core'; import { Router, createUrlTreeFromSnapshot } from '@angular/router'; import { filter, take, switchMap, skipWhile, map } from 'rxjs'; 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 { } class AuthRouteService { #router = inject(Router); #env = inject(AUTH_CONFIG); currentStep = signal('login', ...(ngDevMode ? [{ debugName: "currentStep" }] : [])); currentIdentifier = signal(null, ...(ngDevMode ? [{ debugName: "currentIdentifier" }] : [])); nextStep(state, details = {}) { this.currentStep.set(state); this.currentIdentifier.set(details.identifier ?? 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 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthRouteService, decorators: [{ type: Injectable }] }); 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 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ActionErrorCollectorService, decorators: [{ type: Injectable }] }); const AUTH_CONFIG = new InjectionToken('AUTH_CONFIG'); const configureAuth = (params) => { return [ AuthRouteService, ActionErrorCollectorService, { provide: OmniAuthService, useClass: params.authService, }, { provide: AUTH_CONFIG, useValue: { ...params, identifierType: params.identifierType ?? 'email', }, }, ]; }; 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 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. */ /* eslint-disable max-len */ 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,}))$/; /* eslint-enable max-len */ const usernamePattern = /^[a-zA-Z0-9_.-]*$/; const phonePattern = /^[+]*[(]{0,1}[0-9]{1,4}[)]{0,1}[-\s./0-9]*$/; var patterns$1 = /*#__PURE__*/Object.freeze({ __proto__: null, emailPattern: emailPattern, passwordPattern: passwordPattern, phonePattern: phonePattern, usernamePattern: usernamePattern }); const defaultContentEmail = { loggedIn: { welcomeMessage: 'Welcome back, {{displayName}}!', welcomeMessageNoDisplayName: 'Welcome back!', }, common: { identifierLabel: 'Email', passwordLabel: 'Password', identifierErrorRequiredText: 'Email is required', identifierErrorPatternText: 'Email is invalid', identifierErrorMinLengthText: 'Email needs to be at least 3 characters long', identifierErrorMaxLengthText: 'Email can be maximum 255 characters long', identifierPlaceholder: '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.', linkParagraph: 'A verification link has been sent to your email address.\n' + 'Please check your inbox and click the link to complete your registration.', errorSubmitMessage: 'Error while signing up', submitLabel: 'Verify', resendLabel: 'Resend code', errorResendMessage: 'Error while resending code', }, confirmationSignIn: { subTitleEmail: 'Check your e-mail', linkSentToEmail: 'Login link has been sent to your email account.', errorSubmitMessage: 'Error while signing in', backButton: 'Back to login', submitLabel: 'Confirm', }, 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', sendLinkMessage: 'Enter your e-mail address to receive a login link.', sendLinkLabel: 'Send link', linkSentToEmail: 'Login link has been sent to your email account. Check your inbox and click the link to complete your login.', }, 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', incorrectIdentifierOrPassword: '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', }, }; const defaultContentUsername = { ...defaultContentEmail, common: { ...defaultContentEmail.common, identifierLabel: 'Username', identifierErrorRequiredText: 'Username is required', identifierErrorPatternText: 'Username is invalid', identifierErrorMinLengthText: 'Username needs to be at least 3 characters long', identifierErrorMaxLengthText: 'Username can be maximum 255 characters long', identifierPlaceholder: 'joe.doe', }, errors: { ...defaultContentEmail.errors, incorrectIdentifierOrPassword: 'Password or username is incorrect', } }; /** * @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)); }; }; /** * Generated bundle index. Do not edit. */ export { AUTH_CONFIG, ActionErrorCollectorService, AuthRouteService, ErrorMessagePipe, FlowError, JwtToken, OmniAuthError, OmniAuthService, RuntimeError, TokenProxy, configureAuth, defaultContentEmail, defaultContentUsername, emailToFullName, isError, jwtInterceptor, onlyAuthenticated, onlyGuest, patterns$1 as patterns }; //# sourceMappingURL=ngx-addons-omni-auth-core.mjs.map