@qelos/auth
Version:
Express Passport authentication service
327 lines (293 loc) • 10.1 kB
text/typescript
import { Response, RequestHandler } from 'express'
import { emitPlatformEvent } from '@qelos/api-kit';
import { AuthRequest } from '../../types'
import User from '../models/user'
import UserInternalMetadata from '../models/user-internal-metadata';
import { getEncryptedData, setEncryptedData } from '../services/encrypted-data';
import logger from '../services/logger';
import { isObjectId } from '../../helpers/mongo-utils';
import * as UsersService from '../services/users';
import { Types, Schema } from 'mongoose';
import ObjectId = Types.ObjectId
import { getValidMetadata } from '../services/users';
import Workspace from '../models/workspace';
const privilegedUserFields = 'username phone fullName firstName lastName birthDate roles profileImage socialLogins emailVerified lastLogin';
function getUserIdIfExists(_id, tenant) {
return User.findOne({ _id, tenant }).select('_id').lean().exec();
}
export function getUsersForAdmin(req: AuthRequest, res: Response): void {
try {
// support old versions
const username = req.query.username?.toString().toLowerCase().trim().replace(/ /g, '+') || undefined;
const query: any = {
tenant: req.headers.tenant,
username: req.query.exact ? username ? new RegExp(username, 'i') : undefined : username ? new RegExp(username, 'i') : undefined,
};
if (typeof (req.query as any).roles === 'string') {
query.roles = { $in: (req.query as any).roles.split(',').map((role: string) => role.trim()) };
}
if (req.query._id) {
query._id = { $in: (req.query._id as string).split(',').map(id => new ObjectId(id.trim())) };
}
if (!query.username) {
delete query.username;
}
logger.log('admin db query', query)
User
.find(query)
.select(req.query.select ? req.query.select.toString().replace(/,/g, ' ') : privilegedUserFields)
.lean()
.exec()
.then((users = []) => {
res.json(users).end();
})
.catch((err) => {
logger.error('failed to load users for admin', req.query, err);
res.json([]).end();
})
} catch (err) {
logger.error('failed to load users for admin', req.query, err);
res.json([]).end();
}
}
export function getUsers(req: AuthRequest, res: Response): RequestHandler {
if (!req.query.users) {
getUsersForAdmin(req, res);
return;
}
const users = (req.query.users as string || '')
.split(',')
.map(id => {
const val = id.trim()
if (isObjectId(val)) {
return new ObjectId(val)
}
return false
})
.filter(Boolean)
if (!users.length) {
res.status(200)
.setHeader('x-tenant', req.headers.tenant)
.setHeader('x-user', req.userPayload?.sub || '-')
.set('Content-Type', 'application/json').end('[]')
return;
}
User
.getUsersList(req.headers.tenant, users.filter(Boolean) as any as Schema.Types.ObjectId[], privilegedUserFields)
.then(list => {
res.status(200)
.setHeader('x-tenant', req.headers.tenant)
.setHeader('x-user', 'p-' + req.userPayload.sub)
.set('Content-Type', 'application/json').end(list)
})
.catch(() => res.status(404).json({ message: 'could not load users' }).end())
}
export function getUser(req: AuthRequest, res: Response): RequestHandler {
const isPrivileged = !!(req.userPayload && req.userPayload.isPrivileged)
const promises: Array<Promise<any>> = [
User.findOne({ _id: req.params.userId, tenant: req.headers.tenant })
.select(isPrivileged ? privilegedUserFields : 'fullName')
.lean().exec()
]
if (isPrivileged) {
promises.push(UserInternalMetadata.findOne({
user: req.params.userId,
tenant: req.headers.tenant
}).lean().exec().catch(() => null))
}
Promise.all(promises)
.then(([user, internalMetadata]) => {
if (!user) {
return Promise.reject(null)
}
if (isPrivileged) {
user.internalMetadata = internalMetadata?.metadata || {};
}
res.status(200).json(user).end()
})
.catch(() => res.status(404).json({ message: 'user not exists' }).end())
return;
}
export async function getUserEncryptedData(req: AuthRequest, res: Response) {
const tenant = req.headers.tenant as string;
if (!tenant) {
return res.status(401).end();
}
try {
const user = await getUserIdIfExists(req.params.userId, tenant);
if (!user) {
throw new Error('user not found');
}
const encryptedId = req.headers['x-encrypted-id'];
const id = user._id + (encryptedId ? ('-' + encryptedId) : '');
const { value } = await getEncryptedData(tenant, id);
res.status(200).set('Content-Type', 'application/json').end(value);
} catch (e) {
res.status(200).json(null).end()
}
}
export async function setUserEncryptedData(req: AuthRequest, res: Response) {
const tenant = req.headers.tenant as string;
if (!tenant) {
return res.status(401).end();
}
try {
const user = await getUserIdIfExists(req.params.userId, tenant);
if (!user) {
throw new Error('user not found');
}
const encryptedId = req.headers['x-encrypted-id'];
const id = user._id + (encryptedId ? ('-' + encryptedId) : '');
await setEncryptedData(tenant, id, JSON.stringify(req.body));
res.status(200).set('Content-Type', 'application/json').end('{}');
} catch (e) {
res.status(400).json({ message: 'failed to set encrypted data for user' }).end();
}
}
export async function createUser(req: AuthRequest, res: Response) {
const { tenant, name, internalMetadata, metadata, ...userData } = req.body
const user = new User(userData);
if (req.authConfig.treatUsernameAs === 'email') {
user.username = user.username.toLowerCase().trim().replace(/ /g, '+');
user.email = user.username;
if (!user.username.includes('@')) {
res.status(400).json({ message: 'username should be an email address' }).end();
return;
}
}
user.tenant = req.headers.tenant;
if (!user.fullName && name) {
user.fullName = name;
}
if (metadata) {
try {
user.metadata = getValidMetadata(metadata, req.authConfig.additionalUserFields);
} catch {
// nothing
}
}
try {
if (!user.tenant) {
throw new Error('tenant is missing');
}
const { _id, fullName, firstName, lastName, birthDate, username, roles } = await user.save()
const userInternalMetadata = new UserInternalMetadata({
tenant: req.headers.tenant,
user: _id,
metadata: internalMetadata || {}
});
await userInternalMetadata.save();
const response = { _id, name: fullName, firstName, lastName, birthDate, username, roles, internalMetadata };
emitPlatformEvent({
tenant: req.headers.tenant,
user: _id as string,
source: 'auth',
kind: 'users',
eventName: 'user-created',
description: 'user created by admin endpoint',
metadata: {
user: response
}
})
res.status(200).json(response).end()
} catch (e) {
logger.log('internal user create error', e);
res.status(400).json({ message: 'user creation failed' }).end()
}
}
export async function updateUser(req: AuthRequest, res: Response) {
const {
username,
roles,
name,
password,
fullName,
firstName,
lastName,
birthDate,
profileImage,
internalMetadata
} = req.body || {}
let metadata;
let newInternalMetadata;
try {
if (req.body?.metadata) {
metadata = getValidMetadata(req.body?.metadata, req.authConfig.additionalUserFields);
}
} catch {
metadata = null;
}
try {
await UsersService.updateUser(
{ _id: req.params.userId, tenant: req.headers.tenant },
{ username, roles, password, fullName: fullName || name, firstName, lastName, birthDate, metadata, profileImage },
req.authConfig
)
if (internalMetadata) {
const userInternalMetadata = (await UserInternalMetadata.findOne({
user: req.params.userId,
tenant: req.headers.tenant
}).exec()) || new UserInternalMetadata({
user: req.params.userId,
tenant: req.headers.tenant
})
newInternalMetadata = userInternalMetadata.metadata = Object.assign(userInternalMetadata.metadata || {}, internalMetadata);
userInternalMetadata.markModified('metadata')
await userInternalMetadata.save();
}
const response = {
username,
name: fullName || name,
fullName: fullName || name,
firstName,
lastName,
birthDate,
profileImage,
roles,
internalMetadata: newInternalMetadata || internalMetadata,
_id: req.params.userId
};
emitPlatformEvent({
tenant: req.headers.tenant,
user: req.params.userId,
source: 'auth',
kind: 'users',
eventName: 'user-updated',
description: 'user updated by admin endpoint',
metadata: {
user: response
}
})
res.status(200).json(response).end()
} catch (e) {
res.status(500).json({ message: 'user update failed', info: e?.info }).end()
}
}
export async function removeUser(req: AuthRequest, res: Response) {
try {
await UsersService.deleteUser(req.params.userId, req.headers.tenant);
emitPlatformEvent({
tenant: req.headers.tenant,
user: req.params.userId,
source: 'auth',
kind: 'users',
eventName: 'user-removed',
description: 'user removed by admin endpoint',
metadata: null
})
res.status(200).json({ _id: req.params.userId, tenant: req.headers.tenant }).end()
} catch (e) {
res.status(400).json({ message: 'user deletion failed' }).end()
}
}
export async function getUsersStats(req: AuthRequest, res: Response) {
try {
const [users, workspaces] = await Promise.all([
User.countDocuments({ tenant: req.headers.tenant }).exec(),
Workspace.countDocuments({ tenant: req.headers.tenant }).exec()
]);
res.status(200).json({ users, workspaces }).end();
} catch (e) {
res.status(500).json({ message: 'could not load users stats' }).end();
}
}