@jvhaile/loopback4-helper
Version:
Helper components and tools for loopback 4.
114 lines (100 loc) • 4.67 kB
text/typescript
import {injectable, BindingScope, service, inject} from '@loopback/core';
import {JWTService} from "./jwt.service";
import {HttpErrors} from "@loopback/rest";
import {WhereBuilder} from "@loopback/repository";
import {BaseUserRepository} from "..";
import {BaseUser} from "..";
import {BaseSessionRepository} from "..";
import {BaseSession, UserAgent} from "..";
import {BaseClient} from "..";
import {FirebaseService} from "./firebase.service";
import {FirebaseCredential, FirebaseUser, LoginResult, LoginSource} from "../types";
import {RepositoryBindings} from "../keys";
const _ = require('lodash')
({scope: BindingScope.SINGLETON})
export class AuthenticationService {
constructor(
(FirebaseService) readonly firebaseService: FirebaseService,
(JWTService) readonly jwtService: JWTService,
(RepositoryBindings.USER_REPOSITORY)
readonly userRepository: BaseUserRepository,
(RepositoryBindings.SESSION_REPOSITORY)
readonly sessionRepository: BaseSessionRepository,
) {
}
async signInWithIdToken<T extends BaseUser>(
firebaseCredential: FirebaseCredential,
userExtra: Partial<T>,
source: LoginSource,
allowCreateFromFirebase: boolean = false,
allowedRoles?: string[],
):
Promise<LoginResult<T>> {
let newUser = false;
const firebaseUser = await this.firebaseService.verifyIdTokenAndGetFirebaseUser(
firebaseCredential.firebaseIdToken,
);
let user = await this.userRepository.findOne({
where: new WhereBuilder().or([
new WhereBuilder().eq('firebaseUserId', firebaseUser.uid).build(),
...(firebaseUser.email ? [new WhereBuilder().and([
new WhereBuilder().eq('email', firebaseUser.email).build(),
new WhereBuilder().eq('emailVerified', true).build(),
new WhereBuilder().eq('role', "admin").build(),
]).build()] : []),
]).build()
});
if (user == null) {
newUser = true;
if ((userExtra && userExtra.displayName) || (allowCreateFromFirebase && firebaseUser.displayName)) {
const u = this.buildUser(firebaseUser, userExtra);
user = await this.userRepository.create(u);
} else {
throw new HttpErrors.NotFound("User not found, Please register first!");
}
} else {
const u = this.buildUser(firebaseUser, user);
await this.userRepository.updateById(user.id!, u);
}
if (allowedRoles && allowedRoles.length && !allowedRoles.includes(user.role ?? '')) {
throw new HttpErrors.Forbidden("Access denied, account is not authorized for login!");
}
const session = await this.sessionRepository.create(new BaseSession({
userId: user.id,
clientId: source.clientId,
fcmToken: firebaseCredential.fcmToken,
userAgent: source.userAgent,
loginMethod: firebaseCredential.loginMethod,
}))
const token = await this.jwtService.generateToken(session.toObject());
return {
token,
newUser,
instance: await this.userRepository.findById(user.id!) as T,
}
}
buildUser<T extends BaseUser>(firebaseUser: FirebaseUser, user?: Partial<T>): Partial<T> {
const empty: Partial<T> = {};
return {
...(user ?? empty),
firebaseUserId: firebaseUser.uid,
displayName: user?.displayName || firebaseUser.displayName,
email: user?.email || firebaseUser.email,
emailVerified: user?.emailVerified || firebaseUser.emailVerified,
phone: user?.phone || firebaseUser.phoneNumber,
profilePhotoUrl: user?.profilePhotoUrl || firebaseUser.photoURL,
};
}
async validateTokenAndGetSession(token: any, client: BaseClient, userAgent: UserAgent): Promise<BaseSession> {
const decodedToken = await this.jwtService.verifyToken(token);
const session = new BaseSession(decodedToken);
if (!session.active) throw new HttpErrors.Unauthorized(`Session is deactivated.`);
if (session.clientId != client.id || !this.userAgentMatches(session.userAgent, userAgent)) {
throw new HttpErrors.Unauthorized(`Session source mismatch.`);
}
return session;
}
userAgentMatches(userAgentA?: UserAgent, userAgentB?: UserAgent): boolean {
return userAgentA?.platform == userAgentB?.platform;
}
}