fix bugs + add a lot of endpoints
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
parent
8f503b9c5a
commit
542591342b
16
README.md
16
README.md
|
@ -3,14 +3,14 @@ Backend for movie-web
|
||||||
|
|
||||||
## Todo list
|
## Todo list
|
||||||
- [ ] standard endpoints:
|
- [ ] standard endpoints:
|
||||||
- [ ] make account (PFP, account name)
|
- [X] make account (PFP, account name)
|
||||||
- [ ] login
|
- [X] login (Pending Actual Auth)
|
||||||
- [X] logout a session
|
- [X] logout a session
|
||||||
- [ ] read all sessions from logged in user
|
- [X] read all sessions from logged in user
|
||||||
- [ ] edit current session device name
|
- [X] edit current session device name
|
||||||
- [ ] edit account name and PFP
|
- [X] edit account name and PFP
|
||||||
- [ ] delete logged in user
|
- [X] delete logged in user
|
||||||
- [ ] backend meta (name and description)
|
- [X] backend meta (name and description)
|
||||||
- [ ] upsert settings
|
- [ ] upsert settings
|
||||||
- [ ] upsert watched items
|
- [ ] upsert watched items
|
||||||
- [ ] upsert bookmarks
|
- [ ] upsert bookmarks
|
||||||
|
@ -22,4 +22,6 @@ Backend for movie-web
|
||||||
- [ ] ratelimits (stored in redis)
|
- [ ] ratelimits (stored in redis)
|
||||||
- [ ] switch to pnpm
|
- [ ] switch to pnpm
|
||||||
- [ ] think of privacy centric method of auth
|
- [ ] think of privacy centric method of auth
|
||||||
|
- [ ] Register
|
||||||
|
- [ ] Login
|
||||||
- [ ] global namespacing (accounts are stored on a namespace)
|
- [ ] global namespacing (accounts are stored on a namespace)
|
||||||
|
|
|
@ -15,4 +15,9 @@ export const devFragment: FragmentSchema = {
|
||||||
crypto: {
|
crypto: {
|
||||||
sessionSecret: 'aINCithRivERecKENdmANDRaNKenSiNi',
|
sessionSecret: 'aINCithRivERecKENdmANDRaNKenSiNi',
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
name: 'movie-web development',
|
||||||
|
description:
|
||||||
|
"This backend is only used in development, do not create an account if you this isn't your own instance",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,8 @@ const fragments = {
|
||||||
dockerdev: dockerFragment,
|
dockerdev: dockerFragment,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const version = '1.0.0';
|
||||||
|
|
||||||
export const conf = createConfigLoader()
|
export const conf = createConfigLoader()
|
||||||
.addFromEnvironment('MWB_')
|
.addFromEnvironment('MWB_')
|
||||||
.addFromCLI('mwb-')
|
.addFromCLI('mwb-')
|
||||||
|
|
|
@ -42,4 +42,10 @@ export const configSchema = z.object({
|
||||||
// session secret. used for signing session tokens
|
// session secret. used for signing session tokens
|
||||||
sessionSecret: z.string().min(32),
|
sessionSecret: z.string().min(32),
|
||||||
}),
|
}),
|
||||||
|
meta: z.object({
|
||||||
|
// name and description of this backend
|
||||||
|
// this is displayed to the client when making an account
|
||||||
|
name: z.string().min(1),
|
||||||
|
description: z.string().min(1).optional(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class Session {
|
||||||
|
|
||||||
export interface SessionDTO {
|
export interface SessionDTO {
|
||||||
id: string;
|
id: string;
|
||||||
user: string;
|
userId: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
accessedAt: string;
|
accessedAt: string;
|
||||||
device: string;
|
device: string;
|
||||||
|
@ -37,7 +37,7 @@ export interface SessionDTO {
|
||||||
export function formatSession(session: Session): SessionDTO {
|
export function formatSession(session: Session): SessionDTO {
|
||||||
return {
|
return {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
user: session.id,
|
userId: session.user,
|
||||||
createdAt: session.createdAt.toISOString(),
|
createdAt: session.createdAt.toISOString(),
|
||||||
accessedAt: session.accessedAt.toISOString(),
|
accessedAt: session.accessedAt.toISOString(),
|
||||||
device: session.device,
|
device: session.device,
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { Entity, PrimaryKey, Property, types } from '@mikro-orm/core';
|
import { Entity, PrimaryKey, Property, types } from '@mikro-orm/core';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
export type UserProfile = {
|
||||||
|
colorA: string;
|
||||||
|
colorB: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
|
|
||||||
@Entity({ tableName: 'users' })
|
@Entity({ tableName: 'users' })
|
||||||
export class User {
|
export class User {
|
||||||
@PrimaryKey({ name: 'id', type: 'uuid' })
|
@PrimaryKey({ name: 'id', type: 'uuid' })
|
||||||
|
@ -14,18 +20,36 @@ export class User {
|
||||||
|
|
||||||
@Property({ name: 'permissions', type: types.array })
|
@Property({ name: 'permissions', type: types.array })
|
||||||
roles: string[] = [];
|
roles: string[] = [];
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
name: 'profile',
|
||||||
|
type: types.json,
|
||||||
|
})
|
||||||
|
profile!: UserProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserDTO {
|
export interface UserDTO {
|
||||||
id: string;
|
id: string;
|
||||||
|
name: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
profile: {
|
||||||
|
colorA: string;
|
||||||
|
colorB: string;
|
||||||
|
icon: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatUser(user: User): UserDTO {
|
export function formatUser(user: User): UserDTO {
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
roles: user.roles,
|
roles: user.roles,
|
||||||
createdAt: user.createdAt.toISOString(),
|
createdAt: user.createdAt.toISOString(),
|
||||||
|
profile: {
|
||||||
|
colorA: user.profile.colorA,
|
||||||
|
colorB: user.profile.colorB,
|
||||||
|
icon: user.profile.icon,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ async function bootstrap(): Promise<void> {
|
||||||
evt: 'setup',
|
evt: 'setup',
|
||||||
});
|
});
|
||||||
|
|
||||||
await setupFastify();
|
|
||||||
await setupMikroORM();
|
await setupMikroORM();
|
||||||
|
await setupFastify();
|
||||||
|
|
||||||
log.info(`App setup, ready to accept connections`, {
|
log.info(`App setup, ready to accept connections`, {
|
||||||
evt: 'success',
|
evt: 'success',
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
validatorCompiler,
|
validatorCompiler,
|
||||||
} from 'fastify-type-provider-zod';
|
} from 'fastify-type-provider-zod';
|
||||||
import { ZodError } from 'zod';
|
import { ZodError } from 'zod';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
|
||||||
const log = scopedLogger('fastify');
|
const log = scopedLogger('fastify');
|
||||||
|
|
||||||
|
@ -31,8 +32,8 @@ export async function setupFastify(): Promise<FastifyInstance> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.statusCode) {
|
if (err instanceof StatusError) {
|
||||||
reply.status(err.statusCode).send({
|
reply.status(err.errorStatusCode).send({
|
||||||
errorType: 'message',
|
errorType: 'message',
|
||||||
message: err.message,
|
message: err.message,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
|
import { loginAuthRouter } from '@/routes/auth/login';
|
||||||
import { manageAuthRouter } from '@/routes/auth/manage';
|
import { manageAuthRouter } from '@/routes/auth/manage';
|
||||||
|
import { metaRouter } from '@/routes/meta';
|
||||||
|
import { sessionsRouter } from '@/routes/sessions';
|
||||||
|
import { userDeleteRouter } from '@/routes/users/delete';
|
||||||
|
import { userEditRouter } from '@/routes/users/edit';
|
||||||
|
import { userSessionsRouter } from '@/routes/users/sessions';
|
||||||
import { FastifyInstance } from 'fastify';
|
import { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
export async function setupRoutes(app: FastifyInstance) {
|
export async function setupRoutes(app: FastifyInstance) {
|
||||||
app.register(manageAuthRouter.register);
|
app.register(manageAuthRouter.register);
|
||||||
|
app.register(loginAuthRouter.register);
|
||||||
|
app.register(userSessionsRouter.register);
|
||||||
|
app.register(sessionsRouter.register);
|
||||||
|
app.register(userEditRouter.register);
|
||||||
|
app.register(userDeleteRouter.register);
|
||||||
|
app.register(metaRouter.register);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { formatSession } from '@/db/models/Session';
|
||||||
|
import { User } from '@/db/models/User';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
import { makeSession, makeSessionToken } from '@/services/session';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
const loginSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
device: z.string().max(500).min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const loginAuthRouter = makeRouter((app) => {
|
||||||
|
app.post(
|
||||||
|
'/auth/login',
|
||||||
|
{ schema: { body: loginSchema } },
|
||||||
|
handle(async ({ em, body, req }) => {
|
||||||
|
const user = await em.findOne(User, { id: body.id });
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new StatusError('User cannot be found', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = makeSession(
|
||||||
|
user.id,
|
||||||
|
body.device,
|
||||||
|
req.headers['user-agent'],
|
||||||
|
);
|
||||||
|
|
||||||
|
await em.persistAndFlush(session);
|
||||||
|
|
||||||
|
return {
|
||||||
|
session: formatSession(session),
|
||||||
|
token: makeSessionToken(session),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -8,6 +8,11 @@ import { z } from 'zod';
|
||||||
const registerSchema = z.object({
|
const registerSchema = z.object({
|
||||||
name: z.string().max(500).min(1),
|
name: z.string().max(500).min(1),
|
||||||
device: z.string().max(500).min(1),
|
device: z.string().max(500).min(1),
|
||||||
|
profile: z.object({
|
||||||
|
colorA: z.string(),
|
||||||
|
colorB: z.string(),
|
||||||
|
icon: z.string(),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const manageAuthRouter = makeRouter((app) => {
|
export const manageAuthRouter = makeRouter((app) => {
|
||||||
|
@ -17,14 +22,15 @@ export const manageAuthRouter = makeRouter((app) => {
|
||||||
handle(async ({ em, body, req }) => {
|
handle(async ({ em, body, req }) => {
|
||||||
const user = new User();
|
const user = new User();
|
||||||
user.name = body.name;
|
user.name = body.name;
|
||||||
|
user.profile = body.profile;
|
||||||
|
|
||||||
const session = makeSession(
|
const session = makeSession(
|
||||||
user.id,
|
user.id,
|
||||||
body.device,
|
body.device,
|
||||||
req.headers['user-agent'],
|
req.headers['user-agent'],
|
||||||
);
|
);
|
||||||
|
|
||||||
em.persist([user, session]);
|
await em.persistAndFlush([user, session]);
|
||||||
await em.flush();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: formatUser(user),
|
user: formatUser(user),
|
||||||
|
|
|
@ -4,9 +4,9 @@ import { handle } from '@/services/handler';
|
||||||
import { makeRouter } from '@/services/router';
|
import { makeRouter } from '@/services/router';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const sessionRouter = makeRouter((app) => {
|
export const authSessionRouter = makeRouter((app) => {
|
||||||
app.delete(
|
app.delete(
|
||||||
'/auth/session/:sid',
|
'/sessions/:sid',
|
||||||
{
|
{
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
@ -15,16 +15,21 @@ export const sessionRouter = makeRouter((app) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
handle(async ({ auth, params, em }) => {
|
handle(async ({ auth, params, em }) => {
|
||||||
auth.assert();
|
await auth.assert();
|
||||||
|
|
||||||
const targetedSession = await em.findOne(Session, { id: params.sid });
|
const targetedSession = await em.findOne(Session, { id: params.sid });
|
||||||
if (!targetedSession) return true; // already deleted
|
if (!targetedSession)
|
||||||
|
return {
|
||||||
|
id: params.sid,
|
||||||
|
};
|
||||||
|
|
||||||
if (targetedSession.user !== auth.user.id)
|
if (targetedSession.user !== auth.user.id)
|
||||||
throw new StatusError('Cant delete sessions you dont own', 401);
|
throw new StatusError('Cannot delete sessions you do not own', 401);
|
||||||
|
|
||||||
await em.removeAndFlush(targetedSession);
|
await em.removeAndFlush(targetedSession);
|
||||||
return true;
|
return {
|
||||||
|
id: params.sid,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { conf } from '@/config';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
|
||||||
|
export const metaRouter = makeRouter((app) => {
|
||||||
|
app.get(
|
||||||
|
'/healthcheck',
|
||||||
|
handle(async ({ em }) => {
|
||||||
|
const databaseConnected = await em.config
|
||||||
|
.getDriver()
|
||||||
|
.getConnection()
|
||||||
|
.isConnected();
|
||||||
|
return {
|
||||||
|
healthy: databaseConnected,
|
||||||
|
databaseConnected,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.get(
|
||||||
|
'/meta',
|
||||||
|
handle(async () => {
|
||||||
|
return {
|
||||||
|
name: conf.meta.name,
|
||||||
|
description: conf.meta.description,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { Session, formatSession } from '@/db/models/Session';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const sessionsRouter = makeRouter((app) => {
|
||||||
|
app.patch(
|
||||||
|
'/sessions/:sid',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
sid: z.string(),
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: z.string().max(500).min(1).optional(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handle(async ({ auth, params, em, body }) => {
|
||||||
|
await auth.assert();
|
||||||
|
|
||||||
|
const targetedSession = await em.findOne(Session, { id: params.sid });
|
||||||
|
|
||||||
|
if (!targetedSession)
|
||||||
|
throw new StatusError('Session cannot be found', 404);
|
||||||
|
|
||||||
|
if (targetedSession.user !== auth.user.id)
|
||||||
|
throw new StatusError('Cannot modify sessions you do not own', 401);
|
||||||
|
|
||||||
|
if (body.name) targetedSession.device = body.name;
|
||||||
|
|
||||||
|
await em.persistAndFlush(targetedSession);
|
||||||
|
|
||||||
|
return formatSession(targetedSession);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
app.delete(
|
||||||
|
'/sessions/:sid',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
sid: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handle(async ({ auth, params, em }) => {
|
||||||
|
await auth.assert();
|
||||||
|
|
||||||
|
const targetedSession = await em.findOne(Session, { id: params.sid });
|
||||||
|
if (!targetedSession)
|
||||||
|
return {
|
||||||
|
id: params.sid,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (targetedSession.user !== auth.user.id)
|
||||||
|
throw new StatusError('Cannot delete sessions you do not own', 401);
|
||||||
|
|
||||||
|
await em.removeAndFlush(targetedSession);
|
||||||
|
return {
|
||||||
|
id: params.sid,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Session } from '@/db/models/Session';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const sessionRouter = makeRouter((app) => {
|
||||||
|
app.delete(
|
||||||
|
'/sessions/:sid',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
sid: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handle(async ({ auth, params, em }) => {
|
||||||
|
await auth.assert();
|
||||||
|
|
||||||
|
const targetedSession = await em.findOne(Session, { id: params.sid });
|
||||||
|
if (!targetedSession)
|
||||||
|
return {
|
||||||
|
id: params.sid,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (targetedSession.user !== auth.user.id)
|
||||||
|
throw new StatusError('Cannot delete sessions you do not own', 401);
|
||||||
|
|
||||||
|
await em.removeAndFlush(targetedSession);
|
||||||
|
return {
|
||||||
|
id: params.sid,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Session } from '@/db/models/Session';
|
||||||
|
import { User } from '@/db/models/User';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const userDeleteRouter = makeRouter((app) => {
|
||||||
|
app.delete(
|
||||||
|
'/users/:uid',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
uid: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handle(async ({ auth, params, em }) => {
|
||||||
|
await auth.assert();
|
||||||
|
|
||||||
|
const user = await em.findOne(User, { id: params.uid });
|
||||||
|
if (!user) throw new StatusError('User does not exist', 404);
|
||||||
|
|
||||||
|
if (auth.user.id !== user.id)
|
||||||
|
throw new StatusError('Cannot delete user other than yourself', 403);
|
||||||
|
|
||||||
|
const sessions = await em.find(Session, { user: user.id });
|
||||||
|
|
||||||
|
await em.remove([user, ...sessions]);
|
||||||
|
await em.flush();
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { User, formatUser } from '@/db/models/User';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const userEditRouter = makeRouter((app) => {
|
||||||
|
app.patch(
|
||||||
|
'/users/:uid',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
uid: z.string(),
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
profile: z
|
||||||
|
.object({
|
||||||
|
colorA: z.string(),
|
||||||
|
colorB: z.string(),
|
||||||
|
icon: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
name: z.string().max(500).min(1).optional(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handle(async ({ auth, params, body, em }) => {
|
||||||
|
await auth.assert();
|
||||||
|
|
||||||
|
const user = await em.findOne(User, { id: params.uid });
|
||||||
|
if (!user) throw new StatusError('User does not exist', 404);
|
||||||
|
|
||||||
|
if (auth.user.id !== user.id)
|
||||||
|
throw new StatusError('Cannot modify user other than yourself', 403);
|
||||||
|
|
||||||
|
if (body.name) user.name = body.name;
|
||||||
|
if (body.profile) user.profile = body.profile;
|
||||||
|
|
||||||
|
await em.persistAndFlush(user);
|
||||||
|
return formatUser(user);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { Session, formatSession } from '@/db/models/Session';
|
||||||
|
import { StatusError } from '@/services/error';
|
||||||
|
import { handle } from '@/services/handler';
|
||||||
|
import { makeRouter } from '@/services/router';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const userSessionsRouter = makeRouter((app) => {
|
||||||
|
app.get(
|
||||||
|
'/users/:uid/sessions',
|
||||||
|
{
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
uid: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handle(async ({ auth, params, em }) => {
|
||||||
|
await auth.assert();
|
||||||
|
|
||||||
|
if (auth.user.id !== params.uid)
|
||||||
|
throw new StatusError('Cannot modify user other than yourself', 403);
|
||||||
|
|
||||||
|
const sessions = await em.find(Session, {
|
||||||
|
user: params.uid,
|
||||||
|
});
|
||||||
|
|
||||||
|
return sessions.map(formatSession);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
|
@ -16,10 +16,10 @@ export function makeAuthContext(manager: EntityManager, req: FastifyRequest) {
|
||||||
const header = req.headers.authorization;
|
const header = req.headers.authorization;
|
||||||
if (!header) return null;
|
if (!header) return null;
|
||||||
const [type, token] = header.split(' ', 2);
|
const [type, token] = header.split(' ', 2);
|
||||||
if (type.toLowerCase() !== 'Bearer')
|
if (type.toLowerCase() !== 'bearer')
|
||||||
throw new StatusError('Invalid auth', 400);
|
throw new StatusError('Invalid authentication', 400);
|
||||||
const payload = verifySessionToken(token);
|
const payload = verifySessionToken(token);
|
||||||
if (!payload) throw new StatusError('Invalid auth', 400);
|
if (!payload) throw new StatusError('Invalid authentication', 400);
|
||||||
return payload.sid;
|
return payload.sid;
|
||||||
},
|
},
|
||||||
async getSession() {
|
async getSession() {
|
||||||
|
@ -27,7 +27,7 @@ export function makeAuthContext(manager: EntityManager, req: FastifyRequest) {
|
||||||
const sid = this.getSessionId();
|
const sid = this.getSessionId();
|
||||||
if (!sid) return null;
|
if (!sid) return null;
|
||||||
const session = await getSessionAndBump(em, sid);
|
const session = await getSessionAndBump(em, sid);
|
||||||
if (session) return null;
|
if (!session) return null;
|
||||||
sessionCache = session;
|
sessionCache = session;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
|
@ -42,7 +42,7 @@ export function makeAuthContext(manager: EntityManager, req: FastifyRequest) {
|
||||||
},
|
},
|
||||||
async assert() {
|
async assert() {
|
||||||
const user = await this.getUser();
|
const user = await this.getUser();
|
||||||
if (!user) throw new StatusError('Not logged in', 403);
|
if (!user) throw new StatusError('Not logged in', 401);
|
||||||
return user;
|
return user;
|
||||||
},
|
},
|
||||||
get user() {
|
get user() {
|
||||||
|
@ -56,7 +56,7 @@ export function makeAuthContext(manager: EntityManager, req: FastifyRequest) {
|
||||||
async assertHasRole(role: Roles) {
|
async assertHasRole(role: Roles) {
|
||||||
const user = await this.assert();
|
const user = await this.assert();
|
||||||
const hasRole = user.roles.includes(role);
|
const hasRole = user.roles.includes(role);
|
||||||
if (!hasRole) throw new StatusError('No permissions', 401);
|
if (!hasRole) throw new StatusError('No permissions', 403);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export async function getSessionAndBump(
|
||||||
accessedAt: new Date(),
|
accessedAt: new Date(),
|
||||||
expiresAt: new Date(Date.now() + SESSION_EXPIRY_MS),
|
expiresAt: new Date(Date.now() + SESSION_EXPIRY_MS),
|
||||||
});
|
});
|
||||||
await em.flush();
|
await em.persistAndFlush(session);
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,14 +52,14 @@ export function makeSession(
|
||||||
|
|
||||||
export function makeSessionToken(session: Session): string {
|
export function makeSessionToken(session: Session): string {
|
||||||
return sign({ sid: session.id }, conf.crypto.sessionSecret, {
|
return sign({ sid: session.id }, conf.crypto.sessionSecret, {
|
||||||
algorithm: 'ES512',
|
algorithm: 'HS256',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifySessionToken(token: string): { sid: string } | null {
|
export function verifySessionToken(token: string): { sid: string } | null {
|
||||||
try {
|
try {
|
||||||
const payload = verify(token, conf.crypto.sessionSecret, {
|
const payload = verify(token, conf.crypto.sessionSecret, {
|
||||||
algorithms: ['ES512'],
|
algorithms: ['HS256'],
|
||||||
});
|
});
|
||||||
if (typeof payload === 'string') return null;
|
if (typeof payload === 'string') return null;
|
||||||
return payload as { sid: string };
|
return payload as { sid: string };
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
|
|
Loading…
Reference in New Issue