@ngx-addons/omni-auth-core
Version:
Core library for authentication in Angular applications.
392 lines (373 loc) • 14.3 kB
JavaScript
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