refactor: use next.js middleware for session parsing and move out of custom express server
This commit is contained in:
parent
77701cb6b0
commit
55ceb32fd9
15 changed files with 169 additions and 136 deletions
|
@ -11,15 +11,13 @@
|
|||
"test:client": "jest --colors --selectProjects client --",
|
||||
"test:server": "jest --colors --selectProjects server --",
|
||||
"test:vite": "dotenv -e .env.test -- vitest run --coverage",
|
||||
"dev": "npm run db:migrate && nodemon",
|
||||
"dev": "npm run db:migrate && next dev",
|
||||
"dev:watcher": "pnpm -r --filter cli dev",
|
||||
"db:migrate": "NODE_ENV=development dotenv -e .env -- tsx ./src/server/run-migrations-dev.ts",
|
||||
"start": "NODE_ENV=production node index.js",
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"build": "node ./esbuild.js build",
|
||||
"build:server": "node ./esbuild.js build",
|
||||
"build:next": "next build",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV=production node server.js",
|
||||
"start:dev-container": "./.devcontainer/filewatcher.sh && npm run start:dev",
|
||||
"start:rc": "docker compose -f docker-compose.rc.yml --env-file .env up --build",
|
||||
"start:dev": "npm run prepare && docker compose -f docker-compose.dev.yml up --build",
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getUrl } from '../../core/helpers/url-helpers';
|
|||
import styles from './AppLogo.module.scss';
|
||||
|
||||
export const AppLogo: React.FC<{ id?: string; size?: number; className?: string; alt?: string }> = ({ id, size = 80, className = '', alt = '' }) => {
|
||||
const logoUrl = id ? `/static/apps/${id}/metadata/logo.jpg` : getUrl('placeholder.png');
|
||||
const logoUrl = id ? `/api/app-image?id=${id}` : getUrl('placeholder.png');
|
||||
|
||||
return (
|
||||
<div aria-label={alt} className={clsx(styles.dropShadow, className)} style={{ width: size, height: size }}>
|
||||
|
|
|
@ -9,7 +9,7 @@ const randomCategory = (): AppCategory[] => {
|
|||
return [categories[randomIndex] as AppCategory];
|
||||
};
|
||||
|
||||
export const createApp = (overrides?: Partial<AppInfo>): AppInfo => {
|
||||
const createApp = (overrides?: Partial<AppInfo>): AppInfo => {
|
||||
const name = faker.lorem.word();
|
||||
return {
|
||||
id: name.toLowerCase(),
|
||||
|
@ -62,5 +62,3 @@ export const createAppEntity = (params: CreateAppEntityParams): AppWithInfo => {
|
|||
...overrides,
|
||||
};
|
||||
};
|
||||
|
||||
export const createAppsRandomly = (count: number): AppInfo[] => Array.from({ length: count }).map(() => createApp());
|
||||
|
|
|
@ -2,14 +2,12 @@ import { rest } from 'msw';
|
|||
import SuperJSON from 'superjson';
|
||||
import type { RouterInput, RouterOutput } from '../../server/routers/_app';
|
||||
|
||||
export type RpcResponse<Data> = RpcSuccessResponse<Data> | RpcErrorResponse;
|
||||
|
||||
export type RpcSuccessResponse<Data> = {
|
||||
type RpcSuccessResponse<Data> = {
|
||||
id: null;
|
||||
result: { type: 'data'; data: Data };
|
||||
};
|
||||
|
||||
export type RpcErrorResponse = {
|
||||
type RpcErrorResponse = {
|
||||
error: {
|
||||
json: {
|
||||
message: string;
|
||||
|
|
|
@ -95,7 +95,7 @@ export const SettingsForm = (props: IProps) => {
|
|||
|
||||
const downloadCertificate = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
window.open('/certificate');
|
||||
window.open('/api/certificate');
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -2,9 +2,11 @@ import { GetServerSideProps } from 'next';
|
|||
import merge from 'lodash.merge';
|
||||
import { getLocaleFromString } from '@/shared/internationalization/locales';
|
||||
import { getCookie } from 'cookies-next';
|
||||
import TipiCache from '@/server/core/TipiCache/TipiCache';
|
||||
|
||||
export const getAuthedPageProps: GetServerSideProps = async (ctx) => {
|
||||
const { userId } = ctx.req.session;
|
||||
const sessionId = ctx.req.headers['x-session-id'];
|
||||
const userId = await TipiCache.get(`session:${sessionId}`);
|
||||
|
||||
if (!userId) {
|
||||
return {
|
||||
|
@ -21,11 +23,10 @@ export const getAuthedPageProps: GetServerSideProps = async (ctx) => {
|
|||
};
|
||||
|
||||
export const getMessagesPageProps: GetServerSideProps = async (ctx) => {
|
||||
const { locale: sessionLocale } = ctx.req.session;
|
||||
const cookieLocale = getCookie('tipi-locale', { req: ctx.req });
|
||||
const browserLocale = ctx.req.headers['accept-language']?.split(',')[0];
|
||||
|
||||
const locale = getLocaleFromString(String(sessionLocale || cookieLocale || browserLocale || 'en'));
|
||||
const locale = getLocaleFromString(String(cookieLocale || browserLocale || 'en'));
|
||||
|
||||
const englishMessages = (await import(`../messages/en.json`)).default;
|
||||
const messages = (await import(`../messages/${locale}.json`)).default;
|
||||
|
|
33
src/middleware.ts
Normal file
33
src/middleware.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
/**
|
||||
* Middleware to set session ID in request headers
|
||||
* @param {NextRequest} request - Request object
|
||||
*/
|
||||
export async function middleware(request: NextRequest) {
|
||||
let sessionId = '';
|
||||
|
||||
const cookie = request.cookies.get('tipi.sid')?.value;
|
||||
|
||||
// Check if session ID exists in cookies
|
||||
if (cookie) {
|
||||
sessionId = cookie;
|
||||
}
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
|
||||
if (sessionId) {
|
||||
requestHeaders.set('x-session-id', sessionId);
|
||||
}
|
||||
|
||||
const response = NextResponse.next({
|
||||
request: { headers: requestHeaders },
|
||||
});
|
||||
|
||||
if (sessionId) {
|
||||
response.headers.set('x-session-id', sessionId);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
27
src/pages/api/app-image.ts
Normal file
27
src/pages/api/app-image.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import fs from 'fs';
|
||||
import { getConfig } from '@/server/core/TipiConfig/TipiConfig';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* API endpoint to get the image of an app
|
||||
*
|
||||
* @param {NextApiRequest} req - The request
|
||||
* @param {NextApiResponse} res - The response
|
||||
*/
|
||||
export default function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (typeof req.query.id !== 'string') {
|
||||
res.status(404).send('Not found');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const filePath = path.join(getConfig().rootFolder, 'repos', getConfig().appsRepoId, 'apps', req.query.id, 'metadata', 'logo.jpg');
|
||||
const file = fs.readFileSync(filePath);
|
||||
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
res.send(file);
|
||||
} catch (e) {
|
||||
res.status(404).send('Not found');
|
||||
}
|
||||
}
|
37
src/pages/api/certificate.ts
Normal file
37
src/pages/api/certificate.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { getConfig } from '@/server/core/TipiConfig/TipiConfig';
|
||||
import TipiCache from '@/server/core/TipiCache/TipiCache';
|
||||
import { AuthQueries } from '@/server/queries/auth/auth.queries';
|
||||
import { db } from '@/server/db';
|
||||
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
/**
|
||||
* API endpoint to get the self-signed certificate
|
||||
*
|
||||
* @param {NextApiRequest} req - The request
|
||||
* @param {NextApiResponse} res - The response
|
||||
*/
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const authService = new AuthQueries(db);
|
||||
|
||||
const sessionId = req.headers['x-session-id'];
|
||||
const userId = await TipiCache.get(`session:${sessionId}`);
|
||||
const user = await authService.getUserById(Number(userId));
|
||||
|
||||
if (user?.operator) {
|
||||
const filePath = `${getConfig().rootFolder}/traefik/tls/cert.pem`;
|
||||
|
||||
if (await fs.pathExists(filePath)) {
|
||||
const file = await fs.promises.readFile(filePath);
|
||||
|
||||
res.setHeader('Content-Type', 'application/x-pem-file');
|
||||
res.setHeader('Content-Dispositon', 'attachment; filename=cert.pem');
|
||||
return res.send(file);
|
||||
}
|
||||
|
||||
res.status(404).send('File not found');
|
||||
}
|
||||
|
||||
return res.status(403).send('Forbidden');
|
||||
}
|
|
@ -1,5 +1,20 @@
|
|||
import { setCookie } from 'cookies-next';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { v4 } from 'uuid';
|
||||
import TipiCache from '../core/TipiCache/TipiCache';
|
||||
|
||||
const COOKIE_MAX_AGE = 60 * 60 * 24; // 1 day
|
||||
const COOKIE_NAME = 'tipi.sid';
|
||||
|
||||
export const generateSessionId = (prefix: string) => {
|
||||
return `${prefix}-${v4()}`;
|
||||
};
|
||||
|
||||
export const setSession = async (sessionId: string, userId: string, req: NextApiRequest, res: NextApiResponse) => {
|
||||
setCookie(COOKIE_NAME, sessionId, { req, res, maxAge: COOKIE_MAX_AGE, httpOnly: true, secure: true, sameSite: false });
|
||||
|
||||
const sessionKey = `session:${sessionId}`;
|
||||
|
||||
await TipiCache.set(sessionKey, userId);
|
||||
await TipiCache.set(`session:${userId}:${sessionId}`, sessionKey);
|
||||
};
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import { inferAsyncReturnType } from '@trpc/server';
|
||||
import { CreateNextContextOptions } from '@trpc/server/adapters/next';
|
||||
import { Locale } from '@/shared/internationalization/locales';
|
||||
|
||||
type Session = {
|
||||
userId?: number;
|
||||
locale?: Locale;
|
||||
};
|
||||
import TipiCache from './core/TipiCache/TipiCache';
|
||||
|
||||
type CreateContextOptions = {
|
||||
req: CreateNextContextOptions['req'] & {
|
||||
session?: Session;
|
||||
};
|
||||
req: CreateNextContextOptions['req'];
|
||||
res: CreateNextContextOptions['res'];
|
||||
sessionId: string;
|
||||
userId?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -34,9 +29,15 @@ const createContextInner = async (opts: CreateContextOptions) => ({
|
|||
export const createContext = async (opts: CreateNextContextOptions) => {
|
||||
const { req, res } = opts;
|
||||
|
||||
const sessionId = req.headers['x-session-id'] as string;
|
||||
|
||||
const userId = await TipiCache.get(`session:${sessionId}`);
|
||||
|
||||
return createContextInner({
|
||||
req,
|
||||
res,
|
||||
sessionId,
|
||||
userId: Number(userId) || undefined,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
/* eslint-disable global-require */
|
||||
import express from 'express';
|
||||
import { parse } from 'url';
|
||||
|
||||
import type { NextServer } from 'next/dist/server/next';
|
||||
import { getConfig, setConfig } from './core/TipiConfig';
|
||||
import { Logger } from './core/Logger';
|
||||
import { AppServiceClass } from './services/apps/apps.service';
|
||||
import { db } from './db';
|
||||
import { sessionMiddleware } from './middlewares/session.middleware';
|
||||
import { AuthQueries } from './queries/auth/auth.queries';
|
||||
|
||||
let conf = {};
|
||||
let nextApp: NextServer;
|
||||
|
||||
const hostname = 'localhost';
|
||||
const port = parseInt(process.env.PORT || '3000', 10);
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
if (!dev) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const NextServer = require('next/dist/server/next-server').default;
|
||||
conf = require('./.next/required-server-files.json').config;
|
||||
nextApp = new NextServer({ hostname: 'localhost', dev, port, customServer: true, conf });
|
||||
} else {
|
||||
const next = require('next');
|
||||
nextApp = next({ dev, hostname, port });
|
||||
}
|
||||
|
||||
const handle = nextApp.getRequestHandler();
|
||||
|
||||
nextApp.prepare().then(async () => {
|
||||
const authService = new AuthQueries(db);
|
||||
const app = express();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
|
||||
app.use(sessionMiddleware);
|
||||
|
||||
app.use('/static', express.static(`${getConfig().rootFolder}/repos/${getConfig().appsRepoId}/`));
|
||||
|
||||
app.use('/certificate', async (req, res) => {
|
||||
const userId = req.session?.userId;
|
||||
const user = await authService.getUserById(userId as number);
|
||||
|
||||
if (user?.operator) {
|
||||
res.setHeader('Content-Dispositon', 'attachment; filename=cert.pem');
|
||||
return res.sendFile(`${getConfig().rootFolder}/traefik/tls/cert.pem`);
|
||||
}
|
||||
|
||||
return res.status(403).send('Forbidden');
|
||||
});
|
||||
|
||||
app.all('*', (req, res) => {
|
||||
const parsedUrl = parse(req.url, true);
|
||||
|
||||
handle(req, res, parsedUrl);
|
||||
});
|
||||
|
||||
app.listen(port, async () => {
|
||||
const appService = new AppServiceClass(db);
|
||||
|
||||
setConfig('status', 'RUNNING');
|
||||
|
||||
appService.startAllApps();
|
||||
|
||||
Logger.info(`> Server listening at http://localhost:${port} as ${dev ? 'development' : process.env.NODE_ENV}`);
|
||||
});
|
||||
});
|
|
@ -6,26 +6,26 @@ import { db } from '../../db';
|
|||
const AuthService = new AuthServiceClass(db);
|
||||
|
||||
export const authRouter = router({
|
||||
login: publicProcedure.input(z.object({ username: z.string(), password: z.string() })).mutation(async ({ input, ctx }) => AuthService.login({ ...input }, ctx.req)),
|
||||
logout: protectedProcedure.mutation(async ({ ctx }) => AuthServiceClass.logout(ctx.req)),
|
||||
register: publicProcedure.input(z.object({ username: z.string(), password: z.string(), locale: z.string() })).mutation(async ({ input, ctx }) => AuthService.register({ ...input }, ctx.req)),
|
||||
me: publicProcedure.query(async ({ ctx }) => AuthService.me(ctx.req.session?.userId)),
|
||||
login: publicProcedure.input(z.object({ username: z.string(), password: z.string() })).mutation(async ({ input, ctx }) => AuthService.login({ ...input }, ctx.req, ctx.res)),
|
||||
logout: protectedProcedure.mutation(async ({ ctx }) => AuthServiceClass.logout(ctx.sessionId)),
|
||||
register: publicProcedure
|
||||
.input(z.object({ username: z.string(), password: z.string(), locale: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => AuthService.register({ ...input }, ctx.req, ctx.res)),
|
||||
me: publicProcedure.query(async ({ ctx }) => AuthService.me(ctx.userId)),
|
||||
isConfigured: publicProcedure.query(async () => AuthService.isConfigured()),
|
||||
changeLocale: protectedProcedure
|
||||
.input(z.object({ locale: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => AuthService.changeLocale({ userId: Number(ctx.req.session.userId), locale: input.locale })),
|
||||
changeLocale: protectedProcedure.input(z.object({ locale: z.string() })).mutation(async ({ input, ctx }) => AuthService.changeLocale({ userId: Number(ctx.userId), locale: input.locale })),
|
||||
// Password
|
||||
checkPasswordChangeRequest: publicProcedure.query(AuthServiceClass.checkPasswordChangeRequest),
|
||||
changeOperatorPassword: publicProcedure.input(z.object({ newPassword: z.string() })).mutation(({ input }) => AuthService.changeOperatorPassword({ newPassword: input.newPassword })),
|
||||
cancelPasswordChangeRequest: publicProcedure.mutation(AuthServiceClass.cancelPasswordChangeRequest),
|
||||
changePassword: protectedProcedure
|
||||
.input(z.object({ currentPassword: z.string(), newPassword: z.string() }))
|
||||
.mutation(({ input, ctx }) => AuthService.changePassword({ userId: Number(ctx.req.session.userId), ...input })),
|
||||
.mutation(({ input, ctx }) => AuthService.changePassword({ userId: Number(ctx.userId), ...input })),
|
||||
// Totp
|
||||
verifyTotp: publicProcedure.input(z.object({ totpSessionId: z.string(), totpCode: z.string() })).mutation(({ input, ctx }) => AuthService.verifyTotp(input, ctx.req)),
|
||||
getTotpUri: protectedProcedure.input(z.object({ password: z.string() })).mutation(({ input, ctx }) => AuthService.getTotpUri({ userId: Number(ctx.req.session.userId), password: input.password })),
|
||||
setupTotp: protectedProcedure.input(z.object({ totpCode: z.string() })).mutation(({ input, ctx }) => AuthService.setupTotp({ userId: Number(ctx.req.session.userId), totpCode: input.totpCode })),
|
||||
disableTotp: protectedProcedure.input(z.object({ password: z.string() })).mutation(({ input, ctx }) => AuthService.disableTotp({ userId: Number(ctx.req.session.userId), password: input.password })),
|
||||
verifyTotp: publicProcedure.input(z.object({ totpSessionId: z.string(), totpCode: z.string() })).mutation(({ input, ctx }) => AuthService.verifyTotp(input, ctx.req, ctx.res)),
|
||||
getTotpUri: protectedProcedure.input(z.object({ password: z.string() })).mutation(({ input, ctx }) => AuthService.getTotpUri({ userId: Number(ctx.userId), password: input.password })),
|
||||
setupTotp: protectedProcedure.input(z.object({ totpCode: z.string() })).mutation(({ input, ctx }) => AuthService.setupTotp({ userId: Number(ctx.userId), totpCode: input.totpCode })),
|
||||
disableTotp: protectedProcedure.input(z.object({ password: z.string() })).mutation(({ input, ctx }) => AuthService.disableTotp({ userId: Number(ctx.userId), password: input.password })),
|
||||
});
|
||||
|
||||
export type AuthRouter = typeof authRouter;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import * as argon2 from 'argon2';
|
||||
import validator from 'validator';
|
||||
import { TotpAuthenticator } from '@/server/utils/totp';
|
||||
import { AuthQueries } from '@/server/queries/auth/auth.queries';
|
||||
import { Context } from '@/server/context';
|
||||
import { TranslatedError } from '@/server/utils/errors';
|
||||
import { Locales, getLocaleFromString } from '@/shared/internationalization/locales';
|
||||
import { generateSessionId } from '@/server/common/session.helpers';
|
||||
import { generateSessionId, setSession } from '@/server/common/session.helpers';
|
||||
import { Database } from '@/server/db';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getConfig } from '../../core/TipiConfig';
|
||||
import TipiCache from '../../core/TipiCache';
|
||||
import { fileExists, unlinkFile } from '../../common/fs.helpers';
|
||||
|
@ -29,9 +30,10 @@ export class AuthServiceClass {
|
|||
* Authenticate user with given username and password
|
||||
*
|
||||
* @param {UsernamePasswordInput} input - An object containing the user's username and password
|
||||
* @param {Request} req - The Next.js request object
|
||||
* @param {NextApiRequest} req - The Next.js request object
|
||||
* @param {NextApiResponse} res - The Next.js response object
|
||||
*/
|
||||
public login = async (input: UsernamePasswordInput, req: Context['req']) => {
|
||||
public login = async (input: UsernamePasswordInput, req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { password, username } = input;
|
||||
const user = await this.queries.getUserByUsername(username);
|
||||
|
||||
|
@ -51,8 +53,8 @@ export class AuthServiceClass {
|
|||
return { totpSessionId };
|
||||
}
|
||||
|
||||
req.session.userId = user.id;
|
||||
await TipiCache.set(`session:${user.id}:${req.session.id}`, req.session.id);
|
||||
const sessionId = uuidv4();
|
||||
await setSession(sessionId, user.id.toString(), req, res);
|
||||
|
||||
return {};
|
||||
};
|
||||
|
@ -63,9 +65,10 @@ export class AuthServiceClass {
|
|||
* @param {object} params - An object containing the TOTP session ID and the TOTP code
|
||||
* @param {string} params.totpSessionId - The TOTP session ID
|
||||
* @param {string} params.totpCode - The TOTP code
|
||||
* @param {Request} req - The Next.js request object
|
||||
* @param {NextApiRequest} req - The Next.js request object
|
||||
* @param {NextApiResponse} res - The Next.js response object
|
||||
*/
|
||||
public verifyTotp = async (params: { totpSessionId: string; totpCode: string }, req: Context['req']) => {
|
||||
public verifyTotp = async (params: { totpSessionId: string; totpCode: string }, req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { totpSessionId, totpCode } = params;
|
||||
const userId = await TipiCache.get(totpSessionId);
|
||||
|
||||
|
@ -90,7 +93,8 @@ export class AuthServiceClass {
|
|||
throw new TranslatedError('server-messages.errors.totp-invalid-code');
|
||||
}
|
||||
|
||||
req.session.userId = user.id;
|
||||
const sessionId = uuidv4();
|
||||
await setSession(sessionId, user.id.toString(), req, res);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -195,9 +199,10 @@ export class AuthServiceClass {
|
|||
* Creates a new user with the provided email and password and returns a session token
|
||||
*
|
||||
* @param {UsernamePasswordInput} input - An object containing the email and password fields
|
||||
* @param {Request} req - The Next.js request object
|
||||
* @param {NextApiRequest} req - The Next.js request object
|
||||
* @param {NextApiResponse} res - The Next.js response object
|
||||
*/
|
||||
public register = async (input: UsernamePasswordInput, req: Context['req']) => {
|
||||
public register = async (input: UsernamePasswordInput, req: NextApiRequest, res: NextApiResponse) => {
|
||||
const operators = await this.queries.getOperators();
|
||||
|
||||
if (operators.length > 0) {
|
||||
|
@ -229,8 +234,8 @@ export class AuthServiceClass {
|
|||
throw new TranslatedError('server-messages.errors.error-creating-user');
|
||||
}
|
||||
|
||||
req.session.userId = newUser.id;
|
||||
await TipiCache.set(`session:${newUser.id}:${req.session.id}`, req.session.id);
|
||||
const sessionId = uuidv4();
|
||||
await setSession(sessionId, newUser.id.toString(), req, res);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -253,15 +258,11 @@ export class AuthServiceClass {
|
|||
/**
|
||||
* Logs out the current user by removing the session token
|
||||
*
|
||||
* @param {Request} req - The Next.js request object
|
||||
* @param {string} sessionId - The session token to remove
|
||||
* @returns {Promise<boolean>} - Returns true if the session token is removed successfully
|
||||
*/
|
||||
public static logout = async (req: Context['req']): Promise<boolean> => {
|
||||
if (!req.session) {
|
||||
return true;
|
||||
}
|
||||
|
||||
req.session.destroy(() => {});
|
||||
public static logout = async (sessionId: string): Promise<boolean> => {
|
||||
await TipiCache.del(`session:${sessionId}`);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
@ -345,7 +346,7 @@ export class AuthServiceClass {
|
|||
await Promise.all(
|
||||
sessions.map(async (session) => {
|
||||
await TipiCache.del(session.key);
|
||||
await TipiCache.del(`tipi:${session.val}`);
|
||||
if (session.val) await TipiCache.del(session.val);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { initTRPC, TRPCError } from '@trpc/server';
|
||||
import superjson from 'superjson';
|
||||
import { typeToFlattenedError, ZodError } from 'zod';
|
||||
import { Locale } from '@/shared/internationalization/locales';
|
||||
import { type Context } from './context';
|
||||
import { AuthQueries } from './queries/auth/auth.queries';
|
||||
import { db } from './db';
|
||||
|
@ -53,19 +52,14 @@ export const publicProcedure = t.procedure;
|
|||
* users are logged in
|
||||
*/
|
||||
const isAuthed = t.middleware(async ({ ctx, next }) => {
|
||||
const userId = ctx.req.session?.userId;
|
||||
const { userId } = ctx;
|
||||
if (!userId) {
|
||||
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You need to be logged in to perform this action' });
|
||||
}
|
||||
|
||||
const user = await authQueries.getUserById(userId);
|
||||
|
||||
if (user?.locale) {
|
||||
ctx.req.session.locale = user.locale as Locale;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
ctx.req.session.destroy(() => {});
|
||||
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'You need to be logged in to perform this action' });
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue