Skip to content

Cleaned up auth user types for easier debugging #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 21, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 45 additions & 116 deletions src/auth/user-record.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as utils from '../utils';
import {AuthClientErrorCode, FirebaseAuthError} from '../utils/error';

/**
Expand Down Expand Up @@ -28,26 +29,16 @@ function parseDate(time: any): Date {
* @constructor
*/
export class UserMetadata {
private lastSignedInAtInternal: Date;
private createdAtInternal: Date;
public readonly createdAt: Date;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that readonly here is a compilation time check only. I think it's useful to have to make sure we don't accidentally set the property in our own codebase, but it does not enforce read-only behavior for end-users. That is why I needed to explicitly use Object.defineProperty() below to enforce the read-only behavior.

public readonly lastSignedInAt: Date;

constructor(response: any) {
// Creation date should always be available but due to some backend bugs there
// were cases in the past where users did not have creation date properly set.
// This included legacy Firebase migrating project users and some anonymous users.
// These bugs have already been addressed since then.
this.createdAtInternal = parseDate(response.createdAt);
this.lastSignedInAtInternal = parseDate(response.lastLoginAt);
}

/** @return {Date} The user's last sign-in date. */
public get lastSignedInAt(): Date {
return this.lastSignedInAtInternal;
}

/** @return {Date} The user's account creation date. */
public get createdAt(): Date {
return this.createdAtInternal;
utils.addGetter(this, 'createdAt', parseDate(response.createdAt));
utils.addGetter(this, 'lastSignedInAt', parseDate(response.lastLoginAt));
}

/** @return {Object} The plain object representation of the user's metadata. */
Expand All @@ -68,11 +59,11 @@ export class UserMetadata {
* @constructor
*/
export class UserInfo {
private uidInternal: string;
private displayNameInternal: string;
private emailInternal: string;
private photoURLInternal: string;
private providerIdInternal: string;
public readonly uid: string;
public readonly displayName: string;
public readonly email: string;
public readonly photoURL: string;
public readonly providerId: string;

constructor(response: any) {
// Provider user id and provider id are required.
Expand All @@ -81,36 +72,12 @@ export class UserInfo {
AuthClientErrorCode.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Invalid user info response');
}
this.uidInternal = response.rawId;
this.displayNameInternal = response.displayName;
this.emailInternal = response.email;
this.photoURLInternal = response.photoUrl;
this.providerIdInternal = response.providerId;
}

/** @return {string} The provider user id. */
public get uid(): string {
return this.uidInternal;
}

/** @return {string} The provider display name. */
public get displayName(): string {
return this.displayNameInternal;
}

/** @return {string} The provider email. */
public get email(): string {
return this.emailInternal;
}

/** @return {string} The provider photo URL. */
public get photoURL(): string {
return this.photoURLInternal;
}

/** @return {string} The provider Firebase ID. */
public get providerId(): string {
return this.providerIdInternal;
utils.addGetter(this, 'uid', response.rawId);
utils.addGetter(this, 'displayName', response.displayName);
utils.addGetter(this, 'email', response.email);
utils.addGetter(this, 'photoURL', response.photoUrl);
utils.addGetter(this, 'providerId', response.providerId);
}

/** @return {Object} The plain object representation of the current provider data. */
Expand All @@ -134,14 +101,14 @@ export class UserInfo {
* @constructor
*/
export class UserRecord {
private uidInternal: string;
private emailInternal: string;
private emailVerifiedInternal: boolean;
private displayNameInternal: string;
private photoURLInternal: string;
private disabledInternal: boolean;
private metadataInternal: UserMetadata;
private providerDataInternal: UserInfo[];
public readonly uid: string;
public readonly email: string;
public readonly emailVerified: boolean;
public readonly displayName: string;
public readonly photoURL: string;
public readonly disabled: boolean;
public readonly metadata: UserMetadata;
public readonly providerData: UserInfo[];

constructor(response: any) {
// The Firebase user id is required.
Expand All @@ -150,72 +117,34 @@ export class UserRecord {
AuthClientErrorCode.INTERNAL_ERROR,
'INTERNAL ASSERT FAILED: Invalid user response');
}
this.uidInternal = response.localId;
this.emailInternal = response.email;
this.emailVerifiedInternal = !!response.emailVerified;
this.displayNameInternal = response.displayName;
this.photoURLInternal = response.photoUrl;

utils.addGetter(this, 'uid', response.localId);
utils.addGetter(this, 'email', response.email);
utils.addGetter(this, 'emailVerified', !!response.emailVerified);
utils.addGetter(this, 'displayName', response.displayName);
utils.addGetter(this, 'photoURL', response.photoUrl);
// If disabled is not provided, the account is enabled by default.
this.disabledInternal = response.disabled || false;
this.metadataInternal = new UserMetadata(response);
let providerData: UserInfo[] = response.providerUserInfo || [];
this.providerDataInternal = [];
for (let entry of providerData) {
this.providerData.push(new UserInfo(entry));
utils.addGetter(this, 'disabled', response.disabled || false);
utils.addGetter(this, 'metadata', new UserMetadata(response));
const providerData: UserInfo[] = [];
for (let entry of (response.providerUserInfo || [])) {
providerData.push(new UserInfo(entry));
}
}

/** @return {string} The Firebase user id corresponding to the current user record. */
public get uid(): string {
return this.uidInternal;
}

/** @return {string} The primary email corresponding to the current user record. */
public get email(): string {
return this.emailInternal;
}

/** @return {boolean} Whether the primary email is verified. */
public get emailVerified(): boolean {
return this.emailVerifiedInternal;
}

/** @return {string} The display name corresponding to the current user record. */
public get displayName(): string {
return this.displayNameInternal;
}

/** @return {string} The photo URL corresponding to the current user record. */
public get photoURL(): string {
return this.photoURLInternal;
}

/** @return {boolean} Whether the current user is disabled or not. */
public get disabled(): boolean {
return this.disabledInternal;
}

/** @return {UserMetadata} The user record's metadata. */
public get metadata(): UserMetadata {
return this.metadataInternal;
}

/** @return {UserInfo[]} The list of providers linked to the current record. */
public get providerData(): UserInfo[] {
return this.providerDataInternal;
utils.addGetter(this, 'providerData', providerData);
}

/** @return {Object} The plain object representation of the user record. */
public toJSON(): Object {
let json: any = {};
json.uid = this.uid;
json.email = this.email;
json.emailVerified = this.emailVerified;
json.displayName = this.displayName;
json.photoURL = this.photoURL;
json.disabled = this.disabled;
// Convert metadata to json.
json.metadata = this.metadata.toJSON();
let json: any = {
uid: this.uid,
email: this.email,
emailVerified: this.emailVerified,
displayName: this.displayName,
photoURL: this.photoURL,
disabled: this.disabled,
// Convert metadata to json.
metadata: this.metadata.toJSON(),
};
json.providerData = [];
for (let entry of this.providerData) {
// Convert each provider data to json.
Expand Down
24 changes: 22 additions & 2 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*
* For example, this can be used to map underscore_cased properties to camelCase.
*
* @param {obj} Object The object whose properties to rename.
* @param {keyMap} Object The mapping from old to new property names.
* @param {Object} obj The object whose properties to rename.
* @param {Object} keyMap The mapping from old to new property names.
*/
export function renameProperties(obj: Object, keyMap: { [key: string]: string }): void {
Object.keys(keyMap).forEach((oldKey) => {
Expand All @@ -16,3 +16,23 @@ export function renameProperties(obj: Object, keyMap: { [key: string]: string })
}
});
}

/**
* Defines a new property directly on an object, or modifies an existing property on an object, and
* returns the object.
*
* @param {Object} obj The object on which to define the property.
* @param {string} prop The name of the property to be defined or modified.
* @param {any} value The value associated with the property.
*
* @return {Object} The object that was passed to the function.
*/
export function addGetter(obj: Object, prop: string, value: any): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be renamed to something like addReadonlyGetter or something of the like since a getter does not necessarily imply the property does not have a setter?

Also would be helpful to a test for this, where you add a readonly property on an object, try to change its value and then check the old value is still there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Object.defineProperty(obj, prop, {
value,
// Make this property read-only.
writable: false,
// Include this property during enumeration of obj's properties.
enumerable: true,
});
}