|
16 | 16 | * under the License. |
17 | 17 | */ |
18 | 18 |
|
19 | | -import {LegacyAsgardeoNodeClient} from '@asgardeo/node'; |
| 19 | +import { |
| 20 | + AsgardeoNodeClient, |
| 21 | + AsgardeoRuntimeError, |
| 22 | + AuthClientConfig, |
| 23 | + AuthURLCallback, |
| 24 | + Logger, |
| 25 | + SignInOptions, |
| 26 | + Storage, |
| 27 | + TokenResponse, |
| 28 | + User, |
| 29 | +} from '@asgardeo/node'; |
| 30 | +import express from 'express'; |
| 31 | +import {v4 as generateUUID} from 'uuid'; |
| 32 | +import {CookieConfig, DEFAULT_LOGIN_PATH, DEFAULT_LOGOUT_PATH} from './constants'; |
20 | 33 | import {AsgardeoExpressConfig} from './models/config'; |
| 34 | +import {UnauthenticatedCallback} from './models'; |
| 35 | +import {asgardeoExpressAuth, protectRoute} from './middleware'; |
| 36 | +import {ExpressClientConfig} from './models/express-client-config'; |
| 37 | +import {ExpressUtils} from './utils/ExpressUtils'; |
21 | 38 |
|
22 | | -/** |
23 | | - * Base class for implementing Asgardeo in Express.js based applications. |
24 | | - * This class provides the core functionality for managing user authentication and sessions. |
25 | | - * |
26 | | - * @typeParam T - Configuration type that extends AsgardeoExpressConfig. |
27 | | - */ |
28 | | -abstract class AsgardeoExpressClient<T = AsgardeoExpressConfig> extends LegacyAsgardeoNodeClient<T> {} |
| 39 | +class AsgardeoExpressClient<T = AsgardeoExpressConfig> extends AsgardeoNodeClient<T> { |
| 40 | + private static _clientConfig: ExpressClientConfig; |
| 41 | + private static _instance: AsgardeoExpressClient; |
| 42 | + |
| 43 | + constructor(config: ExpressClientConfig, storage?: Storage) { |
| 44 | + const nodeClientConfig = { |
| 45 | + ...config, |
| 46 | + afterSignInUrl: config.appURL + (config.loginPath ?? DEFAULT_LOGIN_PATH), |
| 47 | + afterSignOutUrl: config.appURL + (config.logoutPath ?? DEFAULT_LOGOUT_PATH), |
| 48 | + } as unknown as AuthClientConfig<T>; |
| 49 | + |
| 50 | + super(nodeClientConfig, storage); |
| 51 | + AsgardeoExpressClient._clientConfig = {...config}; |
| 52 | + } |
| 53 | + |
| 54 | + public static getInstance(config: ExpressClientConfig, storage?: Storage): AsgardeoExpressClient; |
| 55 | + public static getInstance(): AsgardeoExpressClient; |
| 56 | + public static getInstance(config?: ExpressClientConfig, storage?: Storage): AsgardeoExpressClient { |
| 57 | + if (!AsgardeoExpressClient._instance && config) { |
| 58 | + AsgardeoExpressClient._instance = new AsgardeoExpressClient(config, storage); |
| 59 | + Logger.debug('Initialized AsgardeoExpressClient successfully'); |
| 60 | + } |
| 61 | + |
| 62 | + if (!AsgardeoExpressClient._instance && !config) { |
| 63 | + throw new AsgardeoRuntimeError( |
| 64 | + 'User configuration is not found', |
| 65 | + 'EXPRESS-CLIENT-GI1-NF01', |
| 66 | + '@asgardeo/express', |
| 67 | + 'User config has not been passed to initialize AsgardeoExpressClient', |
| 68 | + ); |
| 69 | + } |
| 70 | + |
| 71 | + return AsgardeoExpressClient._instance; |
| 72 | + } |
| 73 | + |
| 74 | + public override signIn( |
| 75 | + req: express.Request, |
| 76 | + res: express.Response, |
| 77 | + next: express.NextFunction, |
| 78 | + signInConfig?: Record<string, string | boolean>, |
| 79 | + ): Promise<TokenResponse>; |
| 80 | + public override signIn( |
| 81 | + authURLCallback: AuthURLCallback, |
| 82 | + userId: string, |
| 83 | + authorizationCode?: string, |
| 84 | + sessionState?: string, |
| 85 | + state?: string, |
| 86 | + signInConfig?: Record<string, string | boolean>, |
| 87 | + ): Promise<TokenResponse>; |
| 88 | + public override signIn(options?: SignInOptions): Promise<User>; |
| 89 | + public override async signIn( |
| 90 | + reqOrCallbackOrOptions?: express.Request | AuthURLCallback | SignInOptions, |
| 91 | + resOrUserId?: express.Response | string, |
| 92 | + nextOrCode?: express.NextFunction | string, |
| 93 | + signInConfigOrSessionState?: Record<string, string | boolean> | string, |
| 94 | + state?: string, |
| 95 | + signInConfig?: Record<string, string | boolean>, |
| 96 | + ): Promise<TokenResponse | User> { |
| 97 | + if (resOrUserId !== undefined && typeof resOrUserId !== 'string') { |
| 98 | + return this._expressSignIn( |
| 99 | + reqOrCallbackOrOptions as express.Request, |
| 100 | + resOrUserId, |
| 101 | + nextOrCode as express.NextFunction, |
| 102 | + signInConfigOrSessionState as Record<string, string | boolean> | undefined, |
| 103 | + ); |
| 104 | + } |
| 105 | + |
| 106 | + if (typeof reqOrCallbackOrOptions === 'function') { |
| 107 | + return super.signIn( |
| 108 | + reqOrCallbackOrOptions, |
| 109 | + resOrUserId as string, |
| 110 | + nextOrCode as string, |
| 111 | + signInConfigOrSessionState as string, |
| 112 | + state, |
| 113 | + signInConfig, |
| 114 | + ); |
| 115 | + } |
| 116 | + |
| 117 | + return super.signIn(reqOrCallbackOrOptions as SignInOptions); |
| 118 | + } |
| 119 | + |
| 120 | + private async _expressSignIn( |
| 121 | + req: express.Request, |
| 122 | + res: express.Response, |
| 123 | + next: express.NextFunction, |
| 124 | + signInConfig?: Record<string, string | boolean>, |
| 125 | + ): Promise<TokenResponse> { |
| 126 | + if (ExpressUtils.hasErrorInURL(req.originalUrl)) { |
| 127 | + return Promise.reject( |
| 128 | + new AsgardeoRuntimeError( |
| 129 | + 'Invalid login request URL', |
| 130 | + 'EXPRESS-CLIENT-SI-IV01', |
| 131 | + '@asgardeo/express', |
| 132 | + 'Login request contains an error query parameter in the URL', |
| 133 | + ), |
| 134 | + ); |
| 135 | + } |
| 136 | + |
| 137 | + let userId: string = req.cookies.ASGARDEO_SESSION_ID; |
| 138 | + if (!userId) { |
| 139 | + userId = generateUUID(); |
| 140 | + } |
| 141 | + |
| 142 | + const authRedirectCallback: AuthURLCallback = (url: string) => { |
| 143 | + if (url) { |
| 144 | + Logger.debug('Redirecting to: ' + url); |
| 145 | + res.cookie('ASGARDEO_SESSION_ID', userId, { |
| 146 | + maxAge: AsgardeoExpressClient._clientConfig.cookieConfig?.maxAge ?? CookieConfig.defaultMaxAge, |
| 147 | + httpOnly: AsgardeoExpressClient._clientConfig.cookieConfig?.httpOnly ?? CookieConfig.defaultHttpOnly, |
| 148 | + sameSite: AsgardeoExpressClient._clientConfig.cookieConfig?.sameSite ?? CookieConfig.defaultSameSite, |
| 149 | + secure: AsgardeoExpressClient._clientConfig.cookieConfig?.secure ?? CookieConfig.defaultSecure, |
| 150 | + }); |
| 151 | + res.redirect(url); |
| 152 | + |
| 153 | + if (typeof next === 'function') { |
| 154 | + next(); |
| 155 | + } |
| 156 | + } |
| 157 | + }; |
| 158 | + |
| 159 | + const authResponse: TokenResponse = await super.signIn( |
| 160 | + authRedirectCallback, |
| 161 | + userId, |
| 162 | + req.query.code as string, |
| 163 | + req.query.session_state as string, |
| 164 | + req.query.state as string, |
| 165 | + signInConfig, |
| 166 | + ); |
| 167 | + |
| 168 | + if (authResponse.accessToken || authResponse.idToken) { |
| 169 | + return authResponse; |
| 170 | + } |
| 171 | + |
| 172 | + return { |
| 173 | + accessToken: '', |
| 174 | + createdAt: 0, |
| 175 | + expiresIn: '', |
| 176 | + idToken: '', |
| 177 | + refreshToken: '', |
| 178 | + scope: '', |
| 179 | + tokenType: '', |
| 180 | + }; |
| 181 | + } |
| 182 | + |
| 183 | + public static protectRoute( |
| 184 | + callback: UnauthenticatedCallback, |
| 185 | + ): (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<void> { |
| 186 | + if (!this._instance) { |
| 187 | + throw new AsgardeoRuntimeError( |
| 188 | + 'AsgardeoExpressClient is not instantiated', |
| 189 | + 'EXPRESS-CLIENT-PR-NF01', |
| 190 | + '@asgardeo/express', |
| 191 | + 'Create an instance of AsgardeoExpressClient before calling this method.', |
| 192 | + ); |
| 193 | + } |
| 194 | + |
| 195 | + return protectRoute(this._instance, callback); |
| 196 | + } |
| 197 | + |
| 198 | + public static asgardeoExpressAuth( |
| 199 | + onSignIn: (res: express.Response, response: TokenResponse) => void, |
| 200 | + onSignOut: (res: express.Response) => void, |
| 201 | + onError: (res: express.Response, exception: AsgardeoRuntimeError) => void, |
| 202 | + ): any { |
| 203 | + if (!this._instance) { |
| 204 | + throw new AsgardeoRuntimeError( |
| 205 | + 'AsgardeoExpressClient is not instantiated', |
| 206 | + 'EXPRESS-CLIENT-AEA-NF01', |
| 207 | + '@asgardeo/express', |
| 208 | + 'Create an instance of AsgardeoExpressClient before calling this method.', |
| 209 | + ); |
| 210 | + } |
| 211 | + |
| 212 | + return asgardeoExpressAuth(this._instance, AsgardeoExpressClient._clientConfig, onSignIn, onSignOut, onError); |
| 213 | + } |
| 214 | +} |
29 | 215 |
|
| 216 | +export {AsgardeoExpressClient}; |
30 | 217 | export default AsgardeoExpressClient; |
0 commit comments