@qelos/auth
Version:
Express Passport authentication service
263 lines (229 loc) • 6.94 kB
text/typescript
import User, { UserDocument, UserModel } from '../models/user';
import { cookieTokenExpiration } from '../../config';
import { Types } from 'mongoose';
import { getAbsoluteDate } from './dates';
import logger from './logger';
import { AuthRequest } from '../../types';
import { verifyToken } from './tokens';
import { IAuthConfigurationMetadata, IUserAdditionalField } from '@qelos/global-types';
export function getValidMetadata(metadata: any = {}, additionalFields: IUserAdditionalField[] = []) {
const result = {};
for (const field of additionalFields) {
if (metadata[field.key] !== undefined && typeof metadata[field.key] === field.valueType) {
result[field.key] = metadata[field.key];
} else if (field.defaultValue) {
result[field.key] = field.defaultValue;
} else if (field.required) {
throw { code: 'INVALID_METADATA' };
}
}
return result;
}
export async function getUser(query: any) {
try {
const user = await User.findOne(query).exec();
if (user) {
return user;
} else {
query.email = query.username;
delete query.username;
const raceUser = await User.findOne(query).exec();
if (raceUser) {
raceUser.username = raceUser.email;
await raceUser.save()
return raceUser;
}
}
} catch (err) {
throw { code: 'FORM_SUBMISSION_FAILED', info: err };
}
throw { code: 'INCORRECT_CREDENTIALS' };
}
export async function updateUser(
user: UserDocument | { _id: Types.ObjectId | string, tenant: string },
{
username = null,
password = null,
fullName = null,
roles = null,
firstName = null,
lastName = null,
birthDate = null,
profileImage = null,
metadata = null
},
authConfig?: IAuthConfigurationMetadata
) {
let directUpdate;
if (!(user instanceof User)) {
if (username || roles || password) {
user = await User.findOne({ _id: user._id, tenant: user.tenant }).exec();
} else {
directUpdate = { _id: user._id, tenant: user.tenant };
user = {} as UserDocument;
}
}
if (fullName) {
user.fullName = fullName;
}
if (username) {
user.username = username;
switch (authConfig?.treatUsernameAs) {
case 'email':
user.email = user.username;
break;
case 'phone':
user.phone = user.username;
break;
default:
break;
}
}
if (password) {
user.password = password;
}
if (firstName) {
user.firstName = firstName;
}
if (lastName) {
user.lastName = lastName;
}
if (birthDate) {
user.birthDate = getAbsoluteDate(birthDate);
}
if (profileImage) {
user.profileImage = profileImage;
}
if (roles) {
user.roles = roles;
}
if (metadata && authConfig) {
user.metadata = getValidMetadata(metadata, authConfig.additionalUserFields);
}
return (directUpdate
? User.updateOne(directUpdate, { $set: user })
: user.save()
).catch((err: Error) => {
logger.error('failed to update user', err)
return Promise.reject({ code: 'UPDATE_USER_FAILED', info: err })
});
}
export async function deleteUser(userId: string, tenant: string) {
if (!tenant) {
throw new Error('tenant is missing');
}
try {
await User.deleteOne({ _id: userId, tenant }).exec();
return { code: 'USER_DELETED_SUCCESSFULLY', info: userId };
} catch (error) {
throw { code: 'USER_DELETE_FAILED', info: error }
}
}
export function comparePassword(user: UserModel, password: string): Promise<UserModel> {
return new Promise((resolve, reject) => {
return user.comparePassword(password.trim(), (passwordErr, isMatch) => {
if (passwordErr) {
return reject({ code: 'FORM_SUBMISSION_FAILED', info: passwordErr });
}
if (!isMatch) {
return reject({ code: 'INCORRECT_CREDENTIALS' });
}
resolve(user);
});
});
}
export function setToken({ user, workspace }: { user: UserDocument, workspace?: any }, authType: string) {
if (authType === 'oauth') {
return setOAuthAuthentication(user, workspace);
}
if (authType === 'cookie') {
return setCookieAuthentication(user, workspace);
}
throw { code: 'INVALID_AUTH_TYPE' };
}
export function updateToken(
user: UserModel,
authType: string,
currentPayload: { tokenIdentifier, workspace?: { _id, name, roles } },
newToken: string
) {
return user
.updateToken(authType, currentPayload, newToken)
.catch((err) => Promise.reject({ code: 'UPDATE_TOKEN_FAILED', info: err }));
}
export async function deleteToken(
tenant: string,
userId: string,
authType: string,
token: string,
isRelatedToken: boolean
) {
try {
const user: any = await User.findOne({ _id: userId, tenant }).exec();
if (isRelatedToken) {
token = await user?.getTokenByRelatedTokens(authType, token);
} else {
token = ((await verifyToken(token, tenant)) as any)?.tokenIdentifier || '';
}
user?.deleteToken(authType, token);
} catch (e) {
return false;
}
return true;
}
function setOAuthAuthentication(user: any, workspace?: any) {
const token = user.getToken({ authType: 'oauth', workspace });
const refreshToken = user.getRefreshToken(token, workspace);
return user.save().then(() => {
return {
token,
refreshToken,
user,
workspace,
};
});
}
function setCookieAuthentication(user: any, workspace?: any) {
const cookieToken = user.getToken({ authType: 'cookie', expiresIn: cookieTokenExpiration / 1000, workspace });
return user.save().then(() => {
return { user, workspace, cookieToken };
});
}
export function getUserIfTokenExists(tenant: string, userId: string, tokenId: string) {
return User.findOne({
_id: userId,
tenant,
'tokens.tokenIdentifier': tokenId,
})
.then((user: any) => user || Promise.reject())
.catch(() => Promise.reject({ code: 'USER_WITH_TOKEN_NOT_EXISTS' }));
}
export async function clearOldTokens(userId: string) {
if (!userId) {
return;
}
const user: any = await User.findOne({ _id: userId }).select('tokens').exec();
if (!user) {
return;
}
const now = Date.now()
const validTokens = user.tokens.filter(t => new Date(t.expiresAt).getTime() > now)
if (validTokens.length === user.tokens) {
return
}
await User.updateOne({ _id: userId }, { $set: { tokens: validTokens } }).exec()
}
export function getCookieTokenName(tenant: string) {
return `qlt_${tenant.substring(0, 8)}`.trim();
}
export function getCookieTokenValue(req: AuthRequest) {
const tokenKey = getCookieTokenName(req.headers.tenant);
return req.signedCookies[tokenKey] || req.cookies[tokenKey] || req.signedCookies.token || req.cookies.token;
}
export async function getUserMetadata(userId: string, tenant: string) {
const user = await User.findOne({
_id: userId,
tenant
}).select('metadata').lean().exec();
return user?.metadata || {};
}