-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: removes deprecated code from Passage class (#209)
* feat!: removes deprecated code from Passage class * feat!: removes deprecated code from User class * feat!: removes deprecated models * feat!: removes unused or deprecated types and utils * feat!: removes public PassageError constructor and swaps some PassageErrors to std Error * feat: adds readonly modifiers to User class fields and moves TokensApi object initialization to constructor * feat: removes auth origin comparison from jwt audience validation (#214) feat: changes jwt validation to only check app id in audience using the jwt libs validation options * feat: removes redundant error message prefixes (#213) * feat!: return void instead of boolean (#212) * refactor: set configs access modifiers in base class instead of inheritied classes * chore: remove exporting generated types
- Loading branch information
Showing
21 changed files
with
118 additions
and
714 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,224 +1,60 @@ | ||
import { AuthStrategy } from '../../types/AuthStrategy'; | ||
import { PassageConfig } from '../../types/PassageConfig'; | ||
import { PassageError } from '../PassageError'; | ||
import { | ||
AppInfo, | ||
AppsApi, | ||
Configuration, | ||
CreateMagicLinkRequest, | ||
MagicLink, | ||
MagicLinksApi, | ||
ResponseError, | ||
} from '../../generated'; | ||
import apiConfiguration from '../../utils/apiConfiguration'; | ||
import { IncomingMessage } from 'http'; | ||
import { getHeaderFromRequest } from '../../utils/getHeader'; | ||
import { PassageInstanceConfig } from '../PassageBase'; | ||
import { Auth } from '../Auth'; | ||
import { User } from '../User'; | ||
import { PassageConfig } from './types'; | ||
import { Configuration, ConfigurationParameters, FetchAPI } from '../../generated'; | ||
|
||
/** | ||
* Passage Class | ||
*/ | ||
export class Passage { | ||
private readonly appId: string; | ||
private _apiKey: string | undefined; | ||
private readonly authStrategy: AuthStrategy; | ||
public readonly user: User; | ||
public readonly auth: Auth; | ||
|
||
private readonly _apiConfiguration: Configuration; | ||
|
||
/** | ||
* Initialize a new Passage instance. | ||
* @param {PassageConfig} config The default config for Passage initialization | ||
*/ | ||
public constructor(config: PassageConfig) { | ||
if (!config.appID) { | ||
throw new PassageError( | ||
'A Passage appID is required. Please include {appID: YOUR_APP_ID, apiKey: YOUR_API_KEY}.', | ||
); | ||
throw new Error('A Passage appID is required. Please include {appID: YOUR_APP_ID, apiKey: YOUR_API_KEY}.'); | ||
} | ||
if (!config.apiKey) { | ||
throw new PassageError( | ||
throw new Error( | ||
'A Passage API Key is required. Please include {appID: YOUR_APP_ID, apiKey: YOUR_API_KEY}.', | ||
); | ||
} | ||
this._apiConfiguration = apiConfiguration({ | ||
accessToken: config.apiKey, | ||
fetchApi: config.fetchApi, | ||
}); | ||
|
||
const instanceConfig: PassageInstanceConfig = { | ||
appId: config.appID, | ||
apiConfiguration: this._apiConfiguration, | ||
apiConfiguration: this.configureApi({ | ||
accessToken: config.apiKey, | ||
fetchApi: config.fetchApi, | ||
}), | ||
}; | ||
|
||
this.user = new User(instanceConfig); | ||
this.auth = new Auth(instanceConfig); | ||
|
||
// To be removed on next major release | ||
this.appId = config.appID; | ||
this._apiKey = config.apiKey; | ||
|
||
this.authStrategy = config?.authStrategy ? config.authStrategy : 'COOKIE'; | ||
} | ||
|
||
/** | ||
* @deprecated Use Passage.auth.validateJwt instead. | ||
* Authenticate request with a cookie, or header. If no authentication | ||
* strategy is given, authenticate the request via cookie (default | ||
* authentication strategy). | ||
* | ||
* @param {IncomingMessage | Request} req Node http request or fetch request | ||
* @return {string} UserID of the Passage user | ||
*/ | ||
async authenticateRequest(req: IncomingMessage | Request): Promise<string> { | ||
if (this.authStrategy == 'HEADER') { | ||
return this.authenticateRequestWithHeader(req); | ||
} else { | ||
return this.authenticateRequestWithCookie(req); | ||
} | ||
} | ||
|
||
/** | ||
* @deprecated Set the API key in the constructor of the Passage object. Do not change API key at runtime. | ||
* Set API key for this Passage instance | ||
* @param {string} _apiKey | ||
*/ | ||
set apiKey(_apiKey) { | ||
this._apiKey = _apiKey; | ||
} | ||
|
||
/** | ||
* @deprecated Getting the API key will be removed in the next major release. | ||
* Get API key for this Passage instance | ||
* @return {string | undefined} Passage API Key | ||
*/ | ||
get apiKey(): string | undefined { | ||
return this._apiKey; | ||
} | ||
|
||
/** | ||
* @deprecated Use Passage.auth.validateJwt instead. | ||
* Authenticate a request via the http header. | ||
* | ||
* @param {IncomingMessage | Request} req Node http request or fetch request | ||
* @return {string} User ID for Passage User | ||
*/ | ||
async authenticateRequestWithHeader(req: IncomingMessage | Request): Promise<string> { | ||
const authorization = getHeaderFromRequest(req, 'authorization'); | ||
|
||
if (!authorization || typeof authorization !== 'string') { | ||
throw new PassageError('Header authorization not found. You must catch this error.'); | ||
} else { | ||
const authToken = (authorization as string).split(' ')[1]; | ||
const userID = await this.validAuthToken(authToken); | ||
if (userID) { | ||
return userID; | ||
} else { | ||
throw new Error('Auth token is invalid'); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @deprecated Use Passage.auth.validateJwt instead. | ||
* Authenticate request via cookie. | ||
* | ||
* @param {IncomingMessage | Request} req Node http request or fetch request | ||
* @return {string} UserID for Passage User | ||
* Configure the API with the provided configuration parameters. | ||
* @param {ConfigurationParameters} config The configuration parameters | ||
* @return {Configuration} The configured API | ||
*/ | ||
async authenticateRequestWithCookie(req: IncomingMessage | Request): Promise<string> { | ||
const cookiesStr = getHeaderFromRequest(req, 'cookie'); | ||
if (!cookiesStr || typeof cookiesStr !== 'string') { | ||
throw new PassageError('Could not find valid cookie for authentication. You must catch this error.'); | ||
} | ||
|
||
const cookies = cookiesStr.split(';'); | ||
let passageAuthToken; | ||
for (const cookie of cookies) { | ||
const sepIdx = cookie.indexOf('='); | ||
if (sepIdx === -1) { | ||
continue; | ||
} | ||
const key = cookie.slice(0, sepIdx).trim(); | ||
if (key !== 'psg_auth_token') { | ||
continue; | ||
} | ||
passageAuthToken = cookie.slice(sepIdx + 1).trim(); | ||
break; | ||
} | ||
|
||
if (passageAuthToken) { | ||
const userID = await this.validAuthToken(passageAuthToken); | ||
if (userID) return userID; | ||
else { | ||
throw new PassageError('Could not validate auth token. You must catch this error.'); | ||
} | ||
} else { | ||
throw new PassageError( | ||
"Could not find authentication cookie 'psg_auth_token' token. You must catch this error.", | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* @deprecated Use Passage.auth.validateJwt instead. | ||
* Determine if the provided token is valid when compared with its | ||
* respective public key. | ||
* | ||
* @param {string} token Authentication token | ||
* @return {string} sub claim if the jwt can be verified, or Error | ||
*/ | ||
async validAuthToken(token: string): Promise<string | undefined> { | ||
return this.auth.validateJwt(token); | ||
} | ||
|
||
/** | ||
* @deprecated Use Passage.auth.createMagicLink instead. | ||
* Create a Magic Link for your app. | ||
* | ||
* @param {CreateMagicLinkRequest} args options for creating a MagicLink. | ||
* @return {Promise<MagicLink>} Passage MagicLink object | ||
*/ | ||
async createMagicLink(args: CreateMagicLinkRequest): Promise<MagicLink> { | ||
try { | ||
const magicLinksApi = new MagicLinksApi(this._apiConfiguration); | ||
const response = await magicLinksApi.createMagicLink({ | ||
appId: this.appId, | ||
createMagicLinkRequest: args, | ||
}); | ||
|
||
return response.magicLink; | ||
} catch (err) { | ||
if (err instanceof ResponseError) { | ||
throw await PassageError.fromResponseError(err, 'Could not create a magic link for this app'); | ||
} | ||
throw err; | ||
} | ||
} | ||
|
||
/** | ||
* @deprecated Passage.auth.validateJwt will validate the JWT audience automatically. | ||
* Get App Info about an app | ||
* | ||
* @return {Promise<AppInfo>} Passage App object | ||
*/ | ||
async getApp(): Promise<AppInfo> { | ||
try { | ||
const client = new AppsApi(this._apiConfiguration); | ||
const response = await client.getApp({ | ||
appId: this.appId, | ||
}); | ||
|
||
return response.app; | ||
} catch (err) { | ||
if (err instanceof ResponseError) { | ||
throw await PassageError.fromResponseError(err, 'Could not fetch app'); | ||
} | ||
private configureApi(config?: ConfigurationParameters): Configuration { | ||
const fetchApi = config?.fetchApi ?? (fetch as unknown as FetchAPI); | ||
const configuration = new Configuration({ | ||
accessToken: config?.accessToken, | ||
fetchApi, | ||
headers: { | ||
...config?.headers, | ||
'Authorization': `Bearer ${config?.accessToken}`, | ||
'Passage-Version': process.env.npm_package_version || '', | ||
}, | ||
middleware: [], | ||
}); | ||
|
||
throw err; | ||
} | ||
return configuration; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,11 @@ | ||
import { FetchAPI } from '../../generated'; | ||
|
||
export { AppInfo, Layouts, LayoutConfig, UserMetadataFieldType, UserMetadataField } from '../../generated'; | ||
export type PassageConfig = { | ||
/** The App ID for your Passage Application. */ | ||
appID: string; | ||
/** The API Key for your Passage Application. */ | ||
apiKey: string; | ||
/** Optional fetch API to use. Will use node-fetch by default if not provided. */ | ||
fetchApi?: FetchAPI; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.