mosquito-transport
Version:
Quickly spawn server infrastructure along robust authentication, database, storage, and cross-platform compatibility
267 lines (236 loc) • 10 kB
JavaScript
import express from "express";
import { decodeBinary, deserializeE2E, encodeBinary, serializeE2E } from "../../helpers/utils.js";
import { ADMIN_DB_NAME, ADMIN_DB_URL, EnginePath, EngineRoutes, ERRORS, NO_CACHE_HEADER } from "../../helpers/values.js";
import { emitDatabase, readDocument } from "../database/index.js";
import { invalidateToken, refreshToken, signinCustom, signupCustom } from "./email_auth.js";
import { doGoogleSignin } from "./google_auth.js";
import { validateJWT } from "./tokenizer.js";
import { simplifyCaughtError } from 'simplify-error';
import { statusErrorCode, useDDOS } from "../../helpers/ddos.js";
import { serialize } from "entity-serializer";
import { doAppleSignin } from "./apple_auth.js";
const {
_listenUserVerification,
_customSignin,
_customSignup,
_refreshAuthToken,
_googleSignin,
_appleSignin,
_facebookSignin,
_twitterSignin,
_githubSignin,
_signOut
} = EngineRoutes;
export const authRouteName = [
_customSignin,
_customSignup,
_refreshAuthToken,
_googleSignin,
_appleSignin,
// _facebookSignin,
// _twitterSignin,
// _githubSignin,
_signOut
];
export const authRoutes = ({
projectName,
logger,
mergeAuthAccount = true,
interceptNewAuth,
googleAuthConfig,
appleAuthConfig,
facebookAuthConfig,
githubAuthConfig,
twitterAuthConfig,
fallbackAuthConfig,
enforceE2E_Encryption,
ddosMap,
internals,
ipNode
}) => [
...enforceE2E_Encryption ? [] : authRouteName.map(v => ({ mroute: v, route: v })),
...authRouteName.map(v => ({ mroute: `e2e/${encodeBinary(v)}`, route: v, ugly: true }))
].map(({ route, mroute, ugly }) =>
express.Router({ caseSensitive: true }).post(`/${mroute}`, async (req, res) => {
const hasLogger = logger.includes('all') || logger.includes('auth'),
now = hasLogger && Date.now();
if (hasLogger) console.log('started route: ', route);
res.set(NO_CACHE_HEADER);
try {
if (
internals?.auth === false ||
(Array.isArray(internals?.auth) && !internals.auth.some(v => v === route))
) throw ERRORS.DISABLE_FEATURE;
const ddosRouting = {
[_customSignup]: 'signup',
[_customSignin]: 'signin',
[_signOut]: 'signout',
[_refreshAuthToken]: 'refresh_token',
[_googleSignin]: 'google_signin',
[_appleSignin]: 'apple_signin'
}[route];
useDDOS(ddosMap, ddosRouting, 'auth', req, ipNode);
let reqBody, clientPublicKey;
if (ugly) {
const [body, clientKey] = await deserializeE2E(req.body, projectName);
reqBody = body;
clientPublicKey = clientKey;
} else reqBody = req.body;
const { data, metadata: metax, token, r_token } = reqBody;
const makeResult = async (b) => {
return ugly ? serialize([await serializeE2E(b, clientPublicKey, projectName)]) : b;
}
switch (route) {
case _customSignup:
const [email, password, name = ''] = data.split('.').map(v => decodeBinary(v));
const result = await signupCustom(email, password, undefined, { email, name: name.trim() || '' }, projectName, { req, metadata: metax, name });
res.status(200).send(await makeResult({ status: 'success', result }));
break;
case _customSignin:
const [e, p] = data.split('.').map(v => decodeBinary(v)),
r1 = await signinCustom(e, p, undefined, projectName);
res.status(200).send(await makeResult({ status: 'success', result: r1 }));
break;
case _signOut:
const r2 = await Promise.all([
invalidateToken(token, projectName),
invalidateToken(r_token, projectName, true),
]);
res.status(200).send(await makeResult({ status: 'success', result: r2 }));
break;
case _refreshAuthToken:
const r4 = await refreshToken({ token, refToken: r_token }, projectName);
res.status(200).send(await makeResult({ status: 'success', result: r4 }));
break;
case _googleSignin:
const r5 = await doGoogleSignin({
googleAuthConfig,
token,
mergeAuthAccount,
projectName
}, req, interceptNewAuth, metax);
res.status(200).send(await makeResult({ status: 'success', result: r5 }));
break;
case _appleSignin:
const r6 = await doAppleSignin({
appleAuthConfig,
token,
mergeAuthAccount,
projectName
}, req, interceptNewAuth, metax);
res.status(200).send(await makeResult({ status: 'success', result: r6 }));
break;
}
} catch (e) {
if (
logger.includes('all') ||
logger.includes('error')
) console.error(`errRoute: /${route} err:`, e);
const result = { status: 'error', ...simplifyCaughtError(e) };
res.status(statusErrorCode(e)).send(ugly ? serialize([undefined, result]) : result);
}
if (hasLogger) console.log(`/${route} took: ${Date.now() - now}ms`);
})
);
export const authLivePath = [
_listenUserVerification
];
/**
* @type {(config: any) => (socket: import('socket.io').Socket)=> void}
*/
export const authLiveRoutesHandler = ({
projectName,
logger,
enforceE2E_Encryption,
internals
}) => (socket) => {
const { auth: initAuthshake } = socket.handshake;
const routeList = [
...enforceE2E_Encryption ? [] : authLivePath.map(v => ({ mroute: v, route: v })),
...authLivePath.map(v => ({ mroute: encodeBinary(v), route: v, ugly: true }))
];
const routeObj = routeList.find(v => v.mroute === initAuthshake._m_route);
if (!routeObj) return;
const { route, ugly } = routeObj;
const hasLogger = logger.includes('all') || logger.includes('auth');
const logError = logger.includes('all') || logger.includes('error');
const now = Date.now();
if (hasLogger) console.log(`plugged socket: /${route}`);
(async () => {
if (route === _listenUserVerification) {
try {
if (
internals?.auth === false ||
(Array.isArray(internals?.auth) && !internals.auth.some(v => v === route))
) throw ERRORS.DISABLE_FEATURE;
let mtoken = initAuthshake.mtoken,
clientPublicKey;
if (ugly) {
const [body, clientKey] = await deserializeE2E(Buffer.from(initAuthshake.e2e, 'base64'), projectName);
mtoken = body.mtoken;
clientPublicKey = clientKey;
}
if (socket.disconnected) return;
let listener, hasDisconnected;
socket.on('disconnect', function () {
listener?.();
hasDisconnected = true;
if (hasLogger) console.log(`/${route} unplugged, live for ${Date.now() - now}ms`);
});
const { uid } = await validateJWT(mtoken, projectName);
if (hasDisconnected) return;
listener = listenUserVerificationState(async verified => {
try {
await validateJWT(mtoken, projectName);
socket.emit('onVerificationChanged', [
undefined,
ugly ? await serializeE2E(!!verified, clientPublicKey, projectName) : !!verified
]);
} catch (e) {
socket.emit('onVerificationChanged', [simplifyCaughtError(e)]);
}
}, err => {
if (logError) console.error(`/${route} err: `, err);
socket.emit('onVerificationChanged', [simplifyCaughtError(err)]);
}, uid, projectName);
} catch (e) {
if (logError) console.error(`errRoute: /${route} err: `, e);
socket.emit('onVerificationChanged', [simplifyCaughtError(e)]);
}
}
})();
}
export const listenUserVerificationState = (callback, onError, uid, projectName) => {
let emittion, lastEmailVerified, hasCancelled, lastProcessID = 0;
const dispatchEvent = async () => {
const thisProcessID = ++lastProcessID;
const data = await readDocument({
path: EnginePath.userAcct,
find: { _id: uid }
}, projectName, ADMIN_DB_NAME, ADMIN_DB_URL);
const thisVerified = data?.passwordVerified;
if (
!hasCancelled &&
thisProcessID === lastProcessID &&
thisVerified !== lastEmailVerified
) {
if (data) {
callback?.(!!thisVerified);
} else onError?.(ERRORS.TOKEN_USER_NOT_FOUND);
lastEmailVerified = thisVerified;
}
}
dispatchEvent();
emittion = emitDatabase(EnginePath.userAcct, async s => {
if (
typeof s.update?.updatedFields?.passwordVerified === 'boolean' &&
s.documentKey === uid
) {
dispatchEvent();
}
}, projectName, ADMIN_DB_NAME, ADMIN_DB_URL);
return () => {
if (!hasCancelled) emittion();
hasCancelled = true;
}
};