Skip to content

Commit

Permalink
refactor(auth): enable user login with different social providers
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavohenke committed Jun 30, 2020
1 parent eb3b579 commit 3ccdfa7
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 50 deletions.
74 changes: 36 additions & 38 deletions auth/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,38 @@ export class MockAuth implements firebase.auth.Auth {
});
}

private async signInWithSocial(provider: firebase.auth.AuthProvider) {
const mock = Array.from(this.socialSignIns.values()).find(
(mock) => mock.type === provider.providerId
);

if (!mock) {
throw new Error("No mock response set.");
}

// Mock is used, then it must go
this.socialSignIns.delete(mock);

const data = await mock.response;
let user = this.store.findByProviderAndEmail(data.email, provider.providerId);
if (user) {
return this.signIn(user, {
isNewUser: false,
providerId: provider.providerId,
profile: null,
username: data.email,
});
}

user = this.store.add({ ...data, providerId: provider.providerId });
return this.signIn(user, {
isNewUser: true,
providerId: provider.providerId,
profile: null,
username: data.email,
});
}

signInAndRetrieveDataWithCredential(credential: firebase.auth.AuthCredential): Promise<any> {
throw new Error("Method not implemented.");
}
Expand Down Expand Up @@ -169,46 +201,12 @@ export class MockAuth implements firebase.auth.Auth {
throw new Error("Method not implemented.");
}

async signInWithPopup(
provider: firebase.auth.AuthProvider
): Promise<firebase.auth.UserCredential> {
const mock = Array.from(this.socialSignIns.values()).find(
(mock) => mock.type === provider.providerId
);

if (!mock) {
throw new Error("No mock response set.");
}

// Mock is used, then it must go
this.socialSignIns.delete(mock);

const data = await mock.response;
let user = this.store.findByEmail(data.email);
if (user) {
if (user.providerId !== provider.providerId) {
throw new Error("auth/account-exists-with-different-credential");
}

return this.signIn(user, {
isNewUser: false,
providerId: provider.providerId,
profile: null,
username: data.email,
});
}

user = this.store.add({ ...data, providerId: provider.providerId });
return this.signIn(user, {
isNewUser: true,
providerId: provider.providerId,
profile: null,
username: data.email,
});
signInWithPopup(provider: firebase.auth.AuthProvider): Promise<firebase.auth.UserCredential> {
return this.signInWithSocial(provider);
}

signInWithRedirect(provider: firebase.auth.AuthProvider): Promise<void> {
throw new Error("Method not implemented.");
async signInWithRedirect(provider: firebase.auth.AuthProvider): Promise<void> {
await this.signInWithSocial(provider);
}

signOut(): Promise<void> {
Expand Down
41 changes: 32 additions & 9 deletions auth/user-store.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,50 @@
import { User, UserSchema } from "./user";
import { User, UserSchema, UserInfo } from "./user";

export class UserStore {
private nextId = 0;
private idStore = new Map<string, UserSchema>();
private emailStore = new Map<string, UserSchema>();
private store = new Map<string, UserSchema>();

add(data: Partial<UserSchema>): User {
const uid = ++this.nextId + "";
const user = new User({ ...data, uid }, this);
const user = new User(
{
...data,
providerData: data.providerId ? [new UserInfo(uid, data.providerId, data)] : [],
uid,
},
this
);

const schema = user.toJSON() as UserSchema;
this.idStore.set(schema.uid, schema);
schema.email && this.emailStore.set(schema.email, schema);
this.store.set(schema.uid, schema);
return user;
}

findByEmail(email: string): User | undefined {
const schema = this.emailStore.get(email);
return schema ? new User(schema, this) : undefined;
for (const user of this.store.values()) {
if (user.email === email) {
return new User(user, this);
}
}

return undefined;
}

findByProviderAndEmail(email: string, providerId: string): User | undefined {
const user = this.findByEmail(email);
if (!user) {
return undefined;
}

if (user.providerData.some((info) => info.providerId === providerId)) {
return new User({ ...user.toJSON(), providerId }, this);
}

throw new Error("auth/account-exists-with-different-credential");
}

update(id: string, data: Partial<UserSchema>) {
const schema = this.idStore.get(id);
const schema = this.store.get(id);
if (!schema) {
return;
}
Expand Down
24 changes: 21 additions & 3 deletions auth/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface UserSchema {
metadata: firebase.auth.UserMetadata;
password?: string;
phoneNumber: string | null;
providerData: (firebase.UserInfo | null)[];
providerData: UserInfo[];
refreshToken: string;
displayName: string | null;
email: string | null;
Expand All @@ -17,7 +17,26 @@ export interface UserSchema {
tenantId: string | null;
}

export class UserInfo implements firebase.UserInfo {
readonly displayName: string | null;
readonly email: string | null;
readonly phoneNumber: string | null;
readonly photoURL: string | null;

constructor(
readonly uid: string,
readonly providerId: string,
rest: Partial<Omit<firebase.UserInfo, "uid" | "providerId">>
) {
this.displayName = rest.displayName || null;
this.email = rest.email || null;
this.phoneNumber = rest.phoneNumber || null;
this.photoURL = rest.photoURL || null;
}
}

export class User implements firebase.User, UserSchema {
readonly uid: string;
displayName: string | null;
email: string | null;
emailVerified: boolean;
Expand All @@ -26,11 +45,10 @@ export class User implements firebase.User, UserSchema {
password?: string;
phoneNumber: string | null;
photoURL: string | null;
providerData: (firebase.UserInfo | null)[];
providerData: UserInfo[];
providerId: string;
refreshToken: string;
tenantId: string | null;
uid: string;
multiFactor: firebase.User.MultiFactorUser;

constructor(data: Partial<UserSchema>, private readonly store: UserStore) {
Expand Down

0 comments on commit 3ccdfa7

Please sign in to comment.