15
15
* limitations under the License.
16
16
*/
17
17
18
- import { FirebaseApp } from '../firebase-app' ;
19
- import { ServiceAccountCredential } from '../credential/credential-internal' ;
20
- import { AuthClientErrorCode , FirebaseAuthError } from '../utils/error' ;
21
- import { AuthorizedHttpClient , HttpError , HttpRequestConfig , HttpClient } from '../utils/api-request ' ;
18
+ import {
19
+ AuthClientErrorCode , ErrorInfo , FirebaseAuthError
20
+ } from '../utils/error' ;
21
+ import { CryptoSigner , CryptoSignerError , CryptoSignerErrorCode } from '../utils/crypto-signer ' ;
22
22
23
23
import * as validator from '../utils/validator' ;
24
24
import { toWebSafeBase64 } from '../utils' ;
25
25
import { Algorithm } from 'jsonwebtoken' ;
26
+ import { HttpError } from '../utils/api-request' ;
26
27
27
-
28
- const ALGORITHM_RS256 : Algorithm = 'RS256' as const ;
29
28
const ALGORITHM_NONE : Algorithm = 'none' as const ;
30
29
31
30
const ONE_HOUR_IN_SECONDS = 60 * 60 ;
@@ -39,32 +38,6 @@ export const BLACKLISTED_CLAIMS = [
39
38
// Audience to use for Firebase Auth Custom tokens
40
39
const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit' ;
41
40
42
- /**
43
- * CryptoSigner interface represents an object that can be used to sign JWTs.
44
- */
45
- export interface CryptoSigner {
46
-
47
- /**
48
- * The name of the signing algorithm.
49
- */
50
- readonly algorithm : Algorithm ;
51
-
52
- /**
53
- * Cryptographically signs a buffer of data.
54
- *
55
- * @param {Buffer } buffer The data to be signed.
56
- * @return {Promise<Buffer> } A promise that resolves with the raw bytes of a signature.
57
- */
58
- sign ( buffer : Buffer ) : Promise < Buffer > ;
59
-
60
- /**
61
- * Returns the ID of the service account used to sign tokens.
62
- *
63
- * @return {Promise<string> } A promise that resolves with a service account ID.
64
- */
65
- getAccountId ( ) : Promise < string > ;
66
- }
67
-
68
41
/**
69
42
* Represents the header of a JWT.
70
43
*/
@@ -87,148 +60,6 @@ interface JWTBody {
87
60
tenant_id ?: string ;
88
61
}
89
62
90
- /**
91
- * A CryptoSigner implementation that uses an explicitly specified service account private key to
92
- * sign data. Performs all operations locally, and does not make any RPC calls.
93
- */
94
- export class ServiceAccountSigner implements CryptoSigner {
95
-
96
- algorithm = ALGORITHM_RS256 ;
97
-
98
- /**
99
- * Creates a new CryptoSigner instance from the given service account credential.
100
- *
101
- * @param {ServiceAccountCredential } credential A service account credential.
102
- */
103
- constructor ( private readonly credential : ServiceAccountCredential ) {
104
- if ( ! credential ) {
105
- throw new FirebaseAuthError (
106
- AuthClientErrorCode . INVALID_CREDENTIAL ,
107
- 'INTERNAL ASSERT: Must provide a service account credential to initialize ServiceAccountSigner.' ,
108
- ) ;
109
- }
110
- }
111
-
112
- /**
113
- * @inheritDoc
114
- */
115
- public sign ( buffer : Buffer ) : Promise < Buffer > {
116
- const crypto = require ( 'crypto' ) ; // eslint-disable-line @typescript-eslint/no-var-requires
117
- const sign = crypto . createSign ( 'RSA-SHA256' ) ;
118
- sign . update ( buffer ) ;
119
- return Promise . resolve ( sign . sign ( this . credential . privateKey ) ) ;
120
- }
121
-
122
- /**
123
- * @inheritDoc
124
- */
125
- public getAccountId ( ) : Promise < string > {
126
- return Promise . resolve ( this . credential . clientEmail ) ;
127
- }
128
- }
129
-
130
- /**
131
- * A CryptoSigner implementation that uses the remote IAM service to sign data. If initialized without
132
- * a service account ID, attempts to discover a service account ID by consulting the local Metadata
133
- * service. This will succeed in managed environments like Google Cloud Functions and App Engine.
134
- *
135
- * @see https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signBlob
136
- * @see https://cloud.google.com/compute/docs/storing-retrieving-metadata
137
- */
138
- export class IAMSigner implements CryptoSigner {
139
- algorithm = ALGORITHM_RS256 ;
140
-
141
- private readonly httpClient : AuthorizedHttpClient ;
142
- private serviceAccountId ?: string ;
143
-
144
- constructor ( httpClient : AuthorizedHttpClient , serviceAccountId ?: string ) {
145
- if ( ! httpClient ) {
146
- throw new FirebaseAuthError (
147
- AuthClientErrorCode . INVALID_ARGUMENT ,
148
- 'INTERNAL ASSERT: Must provide a HTTP client to initialize IAMSigner.' ,
149
- ) ;
150
- }
151
- if ( typeof serviceAccountId !== 'undefined' && ! validator . isNonEmptyString ( serviceAccountId ) ) {
152
- throw new FirebaseAuthError (
153
- AuthClientErrorCode . INVALID_ARGUMENT ,
154
- 'INTERNAL ASSERT: Service account ID must be undefined or a non-empty string.' ,
155
- ) ;
156
- }
157
- this . httpClient = httpClient ;
158
- this . serviceAccountId = serviceAccountId ;
159
- }
160
-
161
- /**
162
- * @inheritDoc
163
- */
164
- public sign ( buffer : Buffer ) : Promise < Buffer > {
165
- return this . getAccountId ( ) . then ( ( serviceAccount ) => {
166
- const request : HttpRequestConfig = {
167
- method : 'POST' ,
168
- url : `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${ serviceAccount } :signBlob` ,
169
- data : { payload : buffer . toString ( 'base64' ) } ,
170
- } ;
171
- return this . httpClient . send ( request ) ;
172
- } ) . then ( ( response : any ) => {
173
- // Response from IAM is base64 encoded. Decode it into a buffer and return.
174
- return Buffer . from ( response . data . signedBlob , 'base64' ) ;
175
- } ) . catch ( ( err ) => {
176
- if ( err instanceof HttpError ) {
177
- const error = err . response . data ;
178
- if ( validator . isNonNullObject ( error ) && error . error ) {
179
- const errorCode = error . error . status ;
180
- const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' +
181
- 'for more details on how to use and troubleshoot this feature.' ;
182
- const errorMsg = `${ error . error . message } ; ${ description } ` ;
183
-
184
- throw FirebaseAuthError . fromServerError ( errorCode , errorMsg , error ) ;
185
- }
186
- throw new FirebaseAuthError (
187
- AuthClientErrorCode . INTERNAL_ERROR ,
188
- 'Error returned from server: ' + error + '. Additionally, an ' +
189
- 'internal error occurred while attempting to extract the ' +
190
- 'errorcode from the error.' ,
191
- ) ;
192
- }
193
- throw err ;
194
- } ) ;
195
- }
196
-
197
- /**
198
- * @inheritDoc
199
- */
200
- public getAccountId ( ) : Promise < string > {
201
- if ( validator . isNonEmptyString ( this . serviceAccountId ) ) {
202
- return Promise . resolve ( this . serviceAccountId ) ;
203
- }
204
- const request : HttpRequestConfig = {
205
- method : 'GET' ,
206
- url : 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email' ,
207
- headers : {
208
- 'Metadata-Flavor' : 'Google' ,
209
- } ,
210
- } ;
211
- const client = new HttpClient ( ) ;
212
- return client . send ( request ) . then ( ( response ) => {
213
- if ( ! response . text ) {
214
- throw new FirebaseAuthError (
215
- AuthClientErrorCode . INTERNAL_ERROR ,
216
- 'HTTP Response missing payload' ,
217
- ) ;
218
- }
219
- this . serviceAccountId = response . text ;
220
- return response . text ;
221
- } ) . catch ( ( err ) => {
222
- throw new FirebaseAuthError (
223
- AuthClientErrorCode . INVALID_CREDENTIAL ,
224
- 'Failed to determine service account. Make sure to initialize ' +
225
- 'the SDK with a service account credential. Alternatively specify a service ' +
226
- `account with iam.serviceAccounts.signBlob permission. Original error: ${ err } ` ,
227
- ) ;
228
- } ) ;
229
- }
230
- }
231
-
232
63
/**
233
64
* A CryptoSigner implementation that is used when communicating with the Auth emulator.
234
65
* It produces unsigned tokens.
@@ -253,22 +84,6 @@ export class EmulatedSigner implements CryptoSigner {
253
84
}
254
85
}
255
86
256
- /**
257
- * Create a new CryptoSigner instance for the given app. If the app has been initialized with a service
258
- * account credential, creates a ServiceAccountSigner. Otherwise creates an IAMSigner.
259
- *
260
- * @param {FirebaseApp } app A FirebaseApp instance.
261
- * @return {CryptoSigner } A CryptoSigner instance.
262
- */
263
- export function cryptoSignerFromApp ( app : FirebaseApp ) : CryptoSigner {
264
- const credential = app . options . credential ;
265
- if ( credential instanceof ServiceAccountCredential ) {
266
- return new ServiceAccountSigner ( credential ) ;
267
- }
268
-
269
- return new IAMSigner ( new AuthorizedHttpClient ( app ) , app . options . serviceAccountId ) ;
270
- }
271
-
272
87
/**
273
88
* Class for generating different types of Firebase Auth tokens (JWTs).
274
89
*/
@@ -361,6 +176,8 @@ export class FirebaseTokenGenerator {
361
176
return Promise . all ( [ token , signPromise ] ) ;
362
177
} ) . then ( ( [ token , signature ] ) => {
363
178
return `${ token } .${ this . encodeSegment ( signature ) } ` ;
179
+ } ) . catch ( ( err ) => {
180
+ throw handleCryptoSignerError ( err ) ;
364
181
} ) ;
365
182
}
366
183
@@ -383,3 +200,44 @@ export class FirebaseTokenGenerator {
383
200
}
384
201
}
385
202
203
+ /**
204
+ * Creates a new FirebaseAuthError by extracting the error code, message and other relevant
205
+ * details from a CryptoSignerError.
206
+ *
207
+ * @param {Error } err The Error to convert into a FirebaseAuthError error
208
+ * @return {FirebaseAuthError } A Firebase Auth error that can be returned to the user.
209
+ */
210
+ export function handleCryptoSignerError ( err : Error ) : Error {
211
+ if ( ! ( err instanceof CryptoSignerError ) ) {
212
+ return err ;
213
+ }
214
+ if ( err . code === CryptoSignerErrorCode . SERVER_ERROR && validator . isNonNullObject ( err . cause ) ) {
215
+ const httpError = err . cause ;
216
+ const errorResponse = ( httpError as HttpError ) . response . data ;
217
+ if ( validator . isNonNullObject ( errorResponse ) && errorResponse . error ) {
218
+ const errorCode = errorResponse . error . status ;
219
+ const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' +
220
+ 'for more details on how to use and troubleshoot this feature.' ;
221
+ const errorMsg = `${ errorResponse . error . message } ; ${ description } ` ;
222
+
223
+ return FirebaseAuthError . fromServerError ( errorCode , errorMsg , errorResponse ) ;
224
+ }
225
+ return new FirebaseAuthError ( AuthClientErrorCode . INTERNAL_ERROR ,
226
+ 'Error returned from server: ' + errorResponse + '. Additionally, an ' +
227
+ 'internal error occurred while attempting to extract the ' +
228
+ 'errorcode from the error.'
229
+ ) ;
230
+ }
231
+ return new FirebaseAuthError ( mapToAuthClientErrorCode ( err . code ) , err . message ) ;
232
+ }
233
+
234
+ function mapToAuthClientErrorCode ( code : string ) : ErrorInfo {
235
+ switch ( code ) {
236
+ case CryptoSignerErrorCode . INVALID_CREDENTIAL :
237
+ return AuthClientErrorCode . INVALID_CREDENTIAL ;
238
+ case CryptoSignerErrorCode . INVALID_ARGUMENT :
239
+ return AuthClientErrorCode . INVALID_ARGUMENT ;
240
+ default :
241
+ return AuthClientErrorCode . INTERNAL_ERROR ;
242
+ }
243
+ }
0 commit comments