diff --git a/spec/common/providers/identity.spec.ts b/spec/common/providers/identity.spec.ts new file mode 100644 index 000000000..fcb686102 --- /dev/null +++ b/spec/common/providers/identity.spec.ts @@ -0,0 +1,88 @@ +// The MIT License (MIT) +// +// Copyright (c) 2022 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import { expect } from 'chai'; +import * as identity from '../../../src/common/providers/identity'; + +describe('identity', () => { + describe('userRecordConstructor', () => { + it('will provide falsey values for fields that are not in raw wire data', () => { + const record = identity.userRecordConstructor({ uid: '123' }); + expect(record.toJSON()).to.deep.equal({ + uid: '123', + email: null, + emailVerified: false, + displayName: null, + photoURL: null, + phoneNumber: null, + disabled: false, + providerData: [], + customClaims: {}, + passwordSalt: null, + passwordHash: null, + tokensValidAfterTime: null, + metadata: { + creationTime: null, + lastSignInTime: null, + }, + }); + }); + + it('will not interfere with fields that are in raw wire data', () => { + const raw: any = { + uid: '123', + email: 'email@gmail.com', + emailVerified: true, + displayName: 'User', + photoURL: 'url', + phoneNumber: '1233332222', + disabled: true, + providerData: [], + customClaims: {}, + passwordSalt: 'abc', + passwordHash: 'def', + tokensValidAfterTime: '2027-02-02T23:01:19.797Z', + metadata: { + creationTime: '2017-02-02T23:06:26.124Z', + lastSignInTime: '2017-02-02T23:01:19.797Z', + }, + }; + const record = identity.userRecordConstructor(raw); + expect(record.toJSON()).to.deep.equal(raw); + }); + + it('will convert raw wire fields createdAt and lastSignedInAt to creationTime and lastSignInTime', () => { + const raw: any = { + uid: '123', + metadata: { + createdAt: '2017-02-02T23:06:26.124Z', + lastSignedInAt: '2017-02-02T23:01:19.797Z', + }, + }; + const record = identity.userRecordConstructor(raw); + expect(record.metadata).to.deep.equal({ + creationTime: '2017-02-02T23:06:26.124Z', + lastSignInTime: '2017-02-02T23:01:19.797Z', + }); + }); + }); +}); diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 6168cf767..8659a1967 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -21,13 +21,12 @@ // SOFTWARE. import { expect } from 'chai'; -import * as firebase from 'firebase-admin'; - import { CloudFunction, Event, EventContext, } from '../../../src/cloud-functions'; +import { UserRecord } from '../../../src/common/providers/identity'; import * as functions from '../../../src/index'; import * as auth from '../../../src/providers/auth'; @@ -75,7 +74,7 @@ describe('Auth Functions', () => { }; } - const handler = (user: firebase.auth.UserRecord) => { + const handler = (user: UserRecord) => { return Promise.resolve(); }; @@ -137,14 +136,12 @@ describe('Auth Functions', () => { }); describe('#_dataConstructor', () => { - let cloudFunctionDelete: CloudFunction; + let cloudFunctionDelete: CloudFunction; before(() => { cloudFunctionDelete = auth .user() - .onDelete( - (data: firebase.auth.UserRecord, context: EventContext) => data - ); + .onDelete((data: UserRecord, context: EventContext) => data); }); it('should handle wire format as of v5.0.0 of firebase-admin', () => { @@ -162,68 +159,6 @@ describe('Auth Functions', () => { }); }); - describe('userRecordConstructor', () => { - it('will provide falsey values for fields that are not in raw wire data', () => { - const record = auth.userRecordConstructor({ uid: '123' }); - expect(record.toJSON()).to.deep.equal({ - uid: '123', - email: null, - emailVerified: false, - displayName: null, - photoURL: null, - phoneNumber: null, - disabled: false, - providerData: [], - customClaims: {}, - passwordSalt: null, - passwordHash: null, - tokensValidAfterTime: null, - metadata: { - creationTime: null, - lastSignInTime: null, - }, - }); - }); - - it('will not interfere with fields that are in raw wire data', () => { - const raw: any = { - uid: '123', - email: 'email@gmail.com', - emailVerified: true, - displayName: 'User', - photoURL: 'url', - phoneNumber: '1233332222', - disabled: true, - providerData: [], - customClaims: {}, - passwordSalt: 'abc', - passwordHash: 'def', - tokensValidAfterTime: '2027-02-02T23:01:19.797Z', - metadata: { - creationTime: '2017-02-02T23:06:26.124Z', - lastSignInTime: '2017-02-02T23:01:19.797Z', - }, - }; - const record = auth.userRecordConstructor(raw); - expect(record.toJSON()).to.deep.equal(raw); - }); - - it('will convert raw wire fields createdAt and lastSignedInAt to creationTime and lastSignInTime', () => { - const raw: any = { - uid: '123', - metadata: { - createdAt: '2017-02-02T23:06:26.124Z', - lastSignedInAt: '2017-02-02T23:01:19.797Z', - }, - }; - const record = auth.userRecordConstructor(raw); - expect(record.metadata).to.deep.equal({ - creationTime: '2017-02-02T23:06:26.124Z', - lastSignInTime: '2017-02-02T23:01:19.797Z', - }); - }); - }); - describe('handler namespace', () => { describe('#onCreate', () => { it('should return an empty trigger', () => { @@ -238,8 +173,8 @@ describe('Auth Functions', () => { }); describe('#onDelete', () => { - const cloudFunctionDelete: CloudFunction = functions.handler.auth.user.onDelete( - (data: firebase.auth.UserRecord) => data + const cloudFunctionDelete: CloudFunction = functions.handler.auth.user.onDelete( + (data: UserRecord) => data ); it('should return an empty trigger', () => { diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts new file mode 100644 index 000000000..24a4e2c34 --- /dev/null +++ b/src/common/providers/identity.ts @@ -0,0 +1,111 @@ +// The MIT License (MIT) +// +// Copyright (c) 2022 Firebase +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import * as firebase from 'firebase-admin'; +import * as _ from 'lodash'; + +/** + * The UserRecord passed to Cloud Functions is the same UserRecord that is returned by the Firebase Admin + * SDK. + */ +export type UserRecord = firebase.auth.UserRecord; + +/** + * UserInfo that is part of the UserRecord + */ +export type UserInfo = firebase.auth.UserInfo; + +/** + * Helper class to create the user metadata in a UserRecord object + */ +export class UserRecordMetadata implements firebase.auth.UserMetadata { + constructor(public creationTime: string, public lastSignInTime: string) {} + + /** Returns a plain JavaScript object with the properties of UserRecordMetadata. */ + toJSON() { + return { + creationTime: this.creationTime, + lastSignInTime: this.lastSignInTime, + }; + } +} + +/** + * Helper function that creates a UserRecord Class from data sent over the wire. + * @param wireData data sent over the wire + * @returns an instance of UserRecord with correct toJSON functions + */ +export function userRecordConstructor(wireData: Object): UserRecord { + // Falsey values from the wire format proto get lost when converted to JSON, this adds them back. + const falseyValues: any = { + email: null, + emailVerified: false, + displayName: null, + photoURL: null, + phoneNumber: null, + disabled: false, + providerData: [], + customClaims: {}, + passwordSalt: null, + passwordHash: null, + tokensValidAfterTime: null, + }; + const record = _.assign({}, falseyValues, wireData); + + const meta = _.get(record, 'metadata'); + if (meta) { + _.set( + record, + 'metadata', + new UserRecordMetadata( + meta.createdAt || meta.creationTime, + meta.lastSignedInAt || meta.lastSignInTime + ) + ); + } else { + _.set(record, 'metadata', new UserRecordMetadata(null, null)); + } + _.forEach(record.providerData, (entry) => { + _.set(entry, 'toJSON', () => { + return entry; + }); + }); + _.set(record, 'toJSON', () => { + const json: any = _.pick(record, [ + 'uid', + 'email', + 'emailVerified', + 'displayName', + 'photoURL', + 'phoneNumber', + 'disabled', + 'passwordHash', + 'passwordSalt', + 'tokensValidAfterTime', + ]); + json.metadata = _.get(record, 'metadata').toJSON(); + json.customClaims = _.cloneDeep(record.customClaims); + json.providerData = _.map(record.providerData, (entry) => entry.toJSON()); + return json; + }); + return record as UserRecord; +} diff --git a/src/providers/auth.ts b/src/providers/auth.ts index 7274399b2..160de06c8 100644 --- a/src/providers/auth.ts +++ b/src/providers/auth.ts @@ -20,8 +20,12 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import * as firebase from 'firebase-admin'; -import * as _ from 'lodash'; +import { + UserRecord, + UserInfo, + UserRecordMetadata, + userRecordConstructor, +} from '../common/providers/identity'; import { CloudFunction, Event, @@ -30,6 +34,9 @@ import { } from '../cloud-functions'; import { DeploymentOptions } from '../function-configuration'; +// TODO: yank in next breaking change release +export { UserRecord, UserInfo, UserRecordMetadata, userRecordConstructor }; + /** @hidden */ export const provider = 'google.firebase.auth'; /** @hidden */ @@ -52,21 +59,9 @@ export function _userWithOptions(options: DeploymentOptions) { }, options); } -export class UserRecordMetadata implements firebase.auth.UserMetadata { - constructor(public creationTime: string, public lastSignInTime: string) {} - - /** Returns a plain JavaScript object with the properties of UserRecordMetadata. */ - toJSON() { - return { - creationTime: this.creationTime, - lastSignInTime: this.lastSignInTime, - }; - } -} - /** Builder used to create Cloud Functions for Firebase Auth user lifecycle events. */ export class UserBuilder { - private static dataConstructor(raw: Event): firebase.auth.UserRecord { + private static dataConstructor(raw: Event): UserRecord { return userRecordConstructor(raw.data); } @@ -109,72 +104,3 @@ export class UserBuilder { }); } } - -/** - * The UserRecord passed to Cloud Functions is the same UserRecord that is returned by the Firebase Admin - * SDK. - */ -export type UserRecord = firebase.auth.UserRecord; - -/** - * UserInfo that is part of the UserRecord - */ -export type UserInfo = firebase.auth.UserInfo; - -export function userRecordConstructor( - wireData: Object -): firebase.auth.UserRecord { - // Falsey values from the wire format proto get lost when converted to JSON, this adds them back. - const falseyValues: any = { - email: null, - emailVerified: false, - displayName: null, - photoURL: null, - phoneNumber: null, - disabled: false, - providerData: [], - customClaims: {}, - passwordSalt: null, - passwordHash: null, - tokensValidAfterTime: null, - }; - const record = _.assign({}, falseyValues, wireData); - - const meta = _.get(record, 'metadata'); - if (meta) { - _.set( - record, - 'metadata', - new UserRecordMetadata( - meta.createdAt || meta.creationTime, - meta.lastSignedInAt || meta.lastSignInTime - ) - ); - } else { - _.set(record, 'metadata', new UserRecordMetadata(null, null)); - } - _.forEach(record.providerData, (entry) => { - _.set(entry, 'toJSON', () => { - return entry; - }); - }); - _.set(record, 'toJSON', () => { - const json: any = _.pick(record, [ - 'uid', - 'email', - 'emailVerified', - 'displayName', - 'photoURL', - 'phoneNumber', - 'disabled', - 'passwordHash', - 'passwordSalt', - 'tokensValidAfterTime', - ]); - json.metadata = _.get(record, 'metadata').toJSON(); - json.customClaims = _.cloneDeep(record.customClaims); - json.providerData = _.map(record.providerData, (entry) => entry.toJSON()); - return json; - }); - return record as firebase.auth.UserRecord; -}