1515 * limitations under the License.
1616 */
1717
18+ import { appCheck } from './index' ;
19+
1820import * as validator from '../utils/validator' ;
19- import { toWebSafeBase64 } from '../utils' ;
21+ import { toWebSafeBase64 , transformMillisecondsToSecondsString } from '../utils' ;
2022
2123import { CryptoSigner , CryptoSignerError , CryptoSignerErrorCode } from '../utils/crypto-signer' ;
2224import {
@@ -26,7 +28,11 @@ import {
2628} from './app-check-api-client-internal' ;
2729import { HttpError } from '../utils/api-request' ;
2830
31+ import AppCheckTokenOptions = appCheck . AppCheckTokenOptions ;
32+
2933const ONE_HOUR_IN_SECONDS = 60 * 60 ;
34+ const ONE_MINUTE_IN_MILLIS = 60 * 1000 ;
35+ const ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000 ;
3036
3137// Audience to use for Firebase App Check Custom tokens
3238const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1beta.TokenExchangeService' ;
@@ -63,12 +69,16 @@ export class AppCheckTokenGenerator {
6369 * @return A Promise fulfilled with a custom token signed with a service account key
6470 * that can be exchanged to an App Check token.
6571 */
66- public createCustomToken ( appId : string ) : Promise < string > {
72+ public createCustomToken ( appId : string , options ?: AppCheckTokenOptions ) : Promise < string > {
6773 if ( ! validator . isNonEmptyString ( appId ) ) {
6874 throw new FirebaseAppCheckError (
6975 'invalid-argument' ,
7076 '`appId` must be a non-empty string.' ) ;
7177 }
78+ let customOptions = { } ;
79+ if ( typeof options !== 'undefined' ) {
80+ customOptions = this . validateTokenOptions ( options ) ;
81+ }
7282 return this . signer . getAccountId ( ) . then ( ( account ) => {
7383 const header = {
7484 alg : this . signer . algorithm ,
@@ -83,6 +93,7 @@ export class AppCheckTokenGenerator {
8393 aud : FIREBASE_APP_CHECK_AUDIENCE ,
8494 exp : iat + ONE_HOUR_IN_SECONDS ,
8595 iat,
96+ ...customOptions ,
8697 } ;
8798 const token = `${ this . encodeSegment ( header ) } .${ this . encodeSegment ( body ) } ` ;
8899 return this . signer . sign ( Buffer . from ( token ) )
@@ -98,6 +109,35 @@ export class AppCheckTokenGenerator {
98109 const buffer : Buffer = ( segment instanceof Buffer ) ? segment : Buffer . from ( JSON . stringify ( segment ) ) ;
99110 return toWebSafeBase64 ( buffer ) . replace ( / = + $ / , '' ) ;
100111 }
112+
113+ /**
114+ * Checks if a given `AppCheckTokenOptions` object is valid. If successful, returns an object with
115+ * custom properties.
116+ *
117+ * @param options An options object to be validated.
118+ * @returns A custom object with ttl converted to protobuf Duration string format.
119+ */
120+ private validateTokenOptions ( options : AppCheckTokenOptions ) : { [ key : string ] : any } {
121+ if ( ! validator . isNonNullObject ( options ) ) {
122+ throw new FirebaseAppCheckError (
123+ 'invalid-argument' ,
124+ 'AppCheckTokenOptions must be a non-null object.' ) ;
125+ }
126+ if ( typeof options . ttlMillis !== 'undefined' ) {
127+ if ( ! validator . isNumber ( options . ttlMillis ) ) {
128+ throw new FirebaseAppCheckError ( 'invalid-argument' ,
129+ 'ttlMillis must be a duration in milliseconds.' ) ;
130+ }
131+ // ttlMillis must be between 30 minutes and 7 days (inclusive)
132+ if ( options . ttlMillis < ( ONE_MINUTE_IN_MILLIS * 30 ) || options . ttlMillis > ( ONE_DAY_IN_MILLIS * 7 ) ) {
133+ throw new FirebaseAppCheckError (
134+ 'invalid-argument' ,
135+ 'ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive).' ) ;
136+ }
137+ return { ttl : transformMillisecondsToSecondsString ( options . ttlMillis ) } ;
138+ }
139+ return { } ;
140+ }
101141}
102142
103143/**
@@ -123,7 +163,7 @@ export function appCheckErrorFromCryptoSignerError(err: Error): Error {
123163 code = APP_CHECK_ERROR_CODE_MAPPING [ status ] ;
124164 }
125165 return new FirebaseAppCheckError ( code ,
126- `Error returned from server while siging a custom token: ${ description } `
166+ `Error returned from server while signing a custom token: ${ description } `
127167 ) ;
128168 }
129169 return new FirebaseAppCheckError ( 'internal-error' ,
0 commit comments