|
1 | | -import { Sha256 } from '@aws-crypto/sha256-js'; |
2 | | -import { |
3 | | - CognitoIdentityClient, |
4 | | - GetCredentialsForIdentityCommand, |
5 | | - GetCredentialsForIdentityCommandOutput, |
6 | | - GetIdCommand, |
7 | | -} from '@aws-sdk/client-cognito-identity'; |
8 | 1 | import { |
9 | 2 | CognitoIdentityProvider, |
10 | 3 | InitiateAuthCommandOutput, |
11 | 4 | } from '@aws-sdk/client-cognito-identity-provider'; |
12 | | -import { HttpRequest } from '@smithy/protocol-http'; |
13 | | -import { SignatureV4 } from '@smithy/signature-v4'; |
14 | 5 | import { |
15 | 6 | AuthenticationDetails, |
16 | 7 | ClientMetadata, |
17 | 8 | CognitoUser, |
18 | 9 | CognitoUserPool, |
19 | 10 | } from 'amazon-cognito-identity-js'; |
| 11 | +import jwt from 'jsonwebtoken'; |
20 | 12 | import { |
21 | 13 | MFAError, |
22 | 14 | MissingFieldError, |
23 | 15 | MissingIdError, |
24 | 16 | TooManyRequestsError, |
25 | 17 | UnauthorizedError, |
26 | | - UnknownError, |
27 | 18 | } from './errors.js'; |
28 | 19 |
|
29 | 20 | export interface UsernamePassword { |
@@ -56,39 +47,69 @@ export interface Credentials { |
56 | 47 | Expiration: Date; |
57 | 48 | } |
58 | 49 |
|
59 | | -function isCompleteCredentials( |
60 | | - cred: GetCredentialsForIdentityCommandOutput['Credentials'] |
61 | | -): cred is Credentials { |
62 | | - return !!cred?.AccessKeyId && !!cred.SecretKey && !!cred.SessionToken && !!cred.Expiration; |
| 50 | +export class SelfManagedSigner { |
| 51 | + /** |
| 52 | + * Creates a signer for Self-Managed JWT auth. |
| 53 | + * @param privateKey - Private key related to the public key provided to Scribe. |
| 54 | + * @param issuer - Issuer as communicated to Scribe. Usually the company name. |
| 55 | + * @param sub - Account id. Provided by Scribe. |
| 56 | + */ |
| 57 | + constructor( |
| 58 | + private privateKey: string, |
| 59 | + private issuer: string, |
| 60 | + private sub: string |
| 61 | + ) {} |
| 62 | + |
| 63 | + /** |
| 64 | + * Signs a JWT with the private key. |
| 65 | + * @param scopes - The scopes to include in the JWT. |
| 66 | + * @param exp - The expiration time of the JWT in seconds. |
| 67 | + * @returns The signed JWT. |
| 68 | + */ |
| 69 | + sign(scopes: string[], exp: number): string { |
| 70 | + const payload = { |
| 71 | + iss: this.issuer, |
| 72 | + sub: this.sub, |
| 73 | + aud: 'https://apis.scribelabs.ai', |
| 74 | + scope: scopes.join(' '), |
| 75 | + exp: exp, |
| 76 | + }; |
| 77 | + |
| 78 | + return jwt.sign(payload, this.privateKey, { algorithm: 'RS256' }); |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +/** |
| 83 | + * Decodes a JWT. |
| 84 | + * @param token - The JWT to decode. |
| 85 | + * @param publicKey - The public key to verify the JWT with. |
| 86 | + * @returns The decoded JWT payload. |
| 87 | + */ |
| 88 | +export function decodeSelfSignedJwt(token: string, publicKey: string): jwt.JwtPayload { |
| 89 | + return jwt.verify(token, publicKey, { |
| 90 | + algorithms: ['RS256'], |
| 91 | + audience: 'https://apis.scribelabs.ai', |
| 92 | + }) as jwt.JwtPayload; |
63 | 93 | } |
64 | 94 |
|
65 | 95 | export class Auth { |
66 | 96 | private client: CognitoIdentityProvider; |
67 | | - private fedClient: CognitoIdentityClient | undefined; |
68 | 97 | private clientId: string; |
69 | 98 | private userPoolId: string; |
70 | | - private identityPoolId: string | undefined; |
71 | 99 |
|
72 | 100 | /** |
73 | 101 | * Construct an authorization client. |
74 | 102 | * @param params - The parameters to construct the client. |
75 | 103 | * @param params.clientId - The client ID of the application provided by Scribe. |
76 | 104 | * @param params.userPoolId - The user pool ID provided by Scribe. |
77 | | - * @param params.identityPoolId - The identity pool ID provided by Scribe. |
78 | 105 | */ |
79 | | - constructor(params: { clientId: string; userPoolId: string; identityPoolId?: string }) { |
| 106 | + constructor(params: { clientId: string; userPoolId: string }) { |
80 | 107 | const region = 'eu-west-2'; |
81 | 108 | this.client = new CognitoIdentityProvider({ |
82 | 109 | region, |
83 | 110 | }); |
84 | 111 | this.clientId = params.clientId; |
85 | 112 | this.userPoolId = params.userPoolId; |
86 | | - this.identityPoolId = params.identityPoolId; |
87 | | - if (params.identityPoolId) { |
88 | | - this.fedClient = new CognitoIdentityClient({ |
89 | | - region, |
90 | | - }); |
91 | | - } |
92 | 113 | } |
93 | 114 |
|
94 | 115 | /** |
@@ -334,101 +355,6 @@ export class Auth { |
334 | 355 | } |
335 | 356 | } |
336 | 357 |
|
337 | | - async getFederatedId(idToken: string): Promise<string> { |
338 | | - /** |
339 | | - * A user gets their federated id. |
340 | | - * |
341 | | - * @param idToken - Id token to use. |
342 | | - * @returns A string containing the federatedId. |
343 | | - */ |
344 | | - if (!this.userPoolId) throw new MissingIdError('Missing user pool ID'); |
345 | | - if (!this.fedClient) |
346 | | - throw new MissingIdError( |
347 | | - 'Identity Pool ID is not provided. Create a new Auth object using identityPoolId' |
348 | | - ); |
349 | | - try { |
350 | | - const response = await this.fedClient.send( |
351 | | - new GetIdCommand({ |
352 | | - IdentityPoolId: this.identityPoolId, |
353 | | - Logins: { |
354 | | - [`cognito-idp.eu-west-2.amazonaws.com/${this.userPoolId}`]: idToken, |
355 | | - }, |
356 | | - }) |
357 | | - ); |
358 | | - if (!response.IdentityId) throw new UnknownError('Could not retrieve federated id'); |
359 | | - return response.IdentityId; |
360 | | - } catch (err) { |
361 | | - if (err instanceof Error && err.name === 'NotAuthorizedException') |
362 | | - throw new UnauthorizedError('Could not retrieve federated id', err); |
363 | | - else if (err instanceof Error && err.name === 'TooManyRequestsException') |
364 | | - throw new TooManyRequestsError('Too many requests. Try again later'); |
365 | | - throw err; |
366 | | - } |
367 | | - } |
368 | | - |
369 | | - async getFederatedCredentials(id: string, idToken: string): Promise<Credentials> { |
370 | | - /** |
371 | | - * A user gets their federated credentials (AccessKeyId, SecretKey and SessionToken). |
372 | | - * |
373 | | - * @param id - Federated id. |
374 | | - * @param idToken - Id token to use. |
375 | | - * @returns Credentials - Object containing the AccessKeyId, SecretKey, SessionToken and Expiration. |
376 | | - * { "AccessKeyId": string, "SecretKey": string, "SessionToken": string, "Expiration": string } |
377 | | - */ |
378 | | - if (!this.userPoolId) throw new MissingIdError('Missing user pool ID'); |
379 | | - if (!this.fedClient) |
380 | | - throw new MissingIdError( |
381 | | - 'Identity Pool ID is not provided. Create a new Auth object using identityPoolId' |
382 | | - ); |
383 | | - try { |
384 | | - const response = await this.fedClient.send( |
385 | | - new GetCredentialsForIdentityCommand({ |
386 | | - IdentityId: id, |
387 | | - Logins: { |
388 | | - [`cognito-idp.eu-west-2.amazonaws.com/${this.userPoolId}`]: idToken, |
389 | | - }, |
390 | | - }) |
391 | | - ); |
392 | | - if (!isCompleteCredentials(response.Credentials)) |
393 | | - throw new UnknownError('Could not retrieve federated credentials'); |
394 | | - return response.Credentials; |
395 | | - } catch (err) { |
396 | | - if (err instanceof Error && err.name === 'NotAuthorizedException') |
397 | | - throw new UnauthorizedError('Could not retrieve federated credentials', err); |
398 | | - else if (err instanceof Error && err.name === 'TooManyRequestsException') |
399 | | - throw new TooManyRequestsError('Too many requests. Try again later'); |
400 | | - else if (err instanceof Error && err.name === 'ResourceNotFoundException') |
401 | | - throw new UnauthorizedError('Federated id incorrect', err); |
402 | | - throw err; |
403 | | - } |
404 | | - } |
405 | | - |
406 | | - async getSignatureForRequest(request: HttpRequest, credentials: Credentials) { |
407 | | - /** |
408 | | - * A user gets a signature for a request. |
409 | | - * |
410 | | - * @param request - Request to send. |
411 | | - * @param credentials - Credentials for the signature creation. |
412 | | - * @returns HeaderBag - Headers containing the signature for the request. |
413 | | - */ |
414 | | - try { |
415 | | - const signer = new SignatureV4({ |
416 | | - credentials: { |
417 | | - accessKeyId: credentials.AccessKeyId, |
418 | | - secretAccessKey: credentials.SecretKey, |
419 | | - sessionToken: credentials.SessionToken, |
420 | | - }, |
421 | | - service: 'execute-api', |
422 | | - region: 'eu-west-2', |
423 | | - sha256: Sha256, |
424 | | - }); |
425 | | - const signatureRequest = await signer.sign(request); |
426 | | - return signatureRequest.headers; |
427 | | - } catch (err) { |
428 | | - throw err; |
429 | | - } |
430 | | - } |
431 | | - |
432 | 358 | // async revokeRefreshToken(refreshToken: string): Promise<boolean> { |
433 | 359 | // /** |
434 | 360 | // Revokes all of the access tokens generated by the specified refresh token. |
|
0 commit comments