-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
315 additions
and
1 deletion.
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
158 changes: 158 additions & 0 deletions
158
@stellar/typescript-wallet-sdk/src/walletSdk/Auth/AuthHeaderSigner.ts
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 |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { AxiosInstance } from "axios"; | ||
import { StrKey } from "@stellar/stellar-sdk"; | ||
import nacl from "tweetnacl"; | ||
import naclUtil from "tweetnacl-util"; | ||
import base64url from "base64url"; | ||
|
||
import { SigningKeypair } from "../Horizon/Account"; | ||
import { DefaultClient } from "../"; | ||
import { AuthHeaderClaims, AuthHeaderCreateTokenParams } from "../Types"; | ||
import { | ||
AuthHeaderSigningKeypairRequiredError, | ||
AuthHeaderClientDomainRequiredError, | ||
} from "../Exceptions"; | ||
|
||
export interface AuthHeaderSigner { | ||
createToken({ | ||
claims, | ||
clientDomain, | ||
issuer, | ||
}: AuthHeaderCreateTokenParams): Promise<string>; | ||
} | ||
|
||
/** | ||
* Signer for signing JWT for GET /Auth with a custodial private key | ||
* | ||
* @class | ||
*/ | ||
export class DefaultAuthHeaderSigner implements AuthHeaderSigner { | ||
expiration: number; | ||
|
||
constructor(expiration: number = 900) { | ||
this.expiration = expiration; | ||
} | ||
|
||
/** | ||
* Create a signed JWT for the auth header | ||
* @constructor | ||
* @param {AuthHeaderCreateTokenParams} params - The create token parameters | ||
* @param {AuthHeaderClaims} params.claims - the data to be signed in the JWT | ||
* @param {string} [params.clientDomain] - the client domain hosting SEP-1 toml | ||
* @param {AccountKeypair} [params.issuer] - the account signing the JWT | ||
* @returns {Promise<string>} The signed JWT | ||
*/ | ||
// eslint-disable-next-line @typescript-eslint/require-await | ||
async createToken({ | ||
claims, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
clientDomain, | ||
issuer, | ||
}: AuthHeaderCreateTokenParams): Promise<string> { | ||
if (!(issuer instanceof SigningKeypair)) { | ||
throw new AuthHeaderSigningKeypairRequiredError(); | ||
} | ||
|
||
const issuedAt = Math.floor(Date.now() / 1000); | ||
const timeExp = Math.floor(Date.now() / 1000) + this.expiration; | ||
|
||
const rawSeed = StrKey.decodeEd25519SecretSeed(issuer.secretKey); | ||
const naclKP = nacl.sign.keyPair.fromSeed(rawSeed); | ||
|
||
const header = { alg: "EdDSA", typ: "JWT" }; | ||
const encodedHeader = base64url(JSON.stringify(header)); | ||
const encodedPayload = base64url( | ||
JSON.stringify({ ...claims, exp: timeExp, iat: issuedAt }), | ||
); | ||
|
||
const signature = nacl.sign.detached( | ||
naclUtil.decodeUTF8(`${encodedHeader}.${encodedPayload}`), | ||
naclKP.secretKey, | ||
); | ||
const encodedSignature = base64url(Buffer.from(signature)); | ||
|
||
const jwt = `${encodedHeader}.${encodedPayload}.${encodedSignature}`; | ||
return jwt; | ||
} | ||
} | ||
|
||
/** | ||
* Signer for signing JWT for GET /Auth using a remote server to sign. | ||
* | ||
* @class | ||
*/ | ||
export class DomainAuthHeaderSigner implements AuthHeaderSigner { | ||
signerUrl: string; | ||
expiration: number; | ||
httpClient: AxiosInstance; | ||
|
||
constructor( | ||
signerUrl: string, | ||
expiration: number = 900, | ||
httpClient?: AxiosInstance, | ||
) { | ||
this.signerUrl = signerUrl; | ||
this.expiration = expiration; | ||
this.httpClient = httpClient || DefaultClient; | ||
} | ||
|
||
/** | ||
* Create a signed JWT for the auth header by using a remote server to sign the JWT | ||
* @constructor | ||
* @param {AuthHeaderCreateTokenParams} params - The create token parameters | ||
* @param {AuthHeaderClaims} params.claims - the data to be signed in the JWT | ||
* @param {string} [params.clientDomain] - the client domain hosting SEP-1 toml | ||
* @param {AccountKeypair} [params.issuer] - unused, will not be used to sign | ||
* @returns {Promise<string>} The signed JWT | ||
*/ | ||
async createToken({ | ||
claims, | ||
clientDomain, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
issuer, | ||
}: AuthHeaderCreateTokenParams): Promise<string> { | ||
if (!clientDomain) { | ||
throw new AuthHeaderClientDomainRequiredError(); | ||
} | ||
|
||
const issuedAt = Math.floor(Date.now() / 1000); | ||
const expiration = Math.floor(Date.now() / 1000) + this.expiration; | ||
|
||
return await this.signTokenRemote({ | ||
claims, | ||
clientDomain, | ||
expiration, | ||
issuedAt, | ||
}); | ||
} | ||
|
||
/** | ||
* Sign JWT by calling a remote server | ||
* @constructor | ||
* @param {SignTokenRemoteParams} params - the sign token params | ||
* @param {AuthHeaderClaims} params.claims - the data to be signed in the JWT | ||
* @param {string} params.clientDomain - the client domain hosting SEP-1 toml | ||
* @param {number} params.expiration - when the token should expire | ||
* @param {number} params.issuedAt - when the token was created | ||
* @returns {Promise<string>} The signed JWT | ||
*/ | ||
async signTokenRemote({ | ||
claims, | ||
clientDomain, | ||
expiration, | ||
issuedAt, | ||
}: { | ||
claims: AuthHeaderClaims; | ||
clientDomain: string; | ||
expiration: number; | ||
issuedAt: number; | ||
}): Promise<string> { | ||
const resp = await this.httpClient.post(this.signerUrl, { | ||
clientDomain, | ||
expiration, | ||
issuedAt, | ||
...claims, | ||
}); | ||
|
||
return resp.data.token; | ||
} | ||
} |
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
Oops, something went wrong.