This commit is contained in:
@@ -1,7 +1,29 @@
|
||||
import { PrismaAdapter } from '@lucia-auth/adapter-prisma'
|
||||
import { Lucia } from 'lucia'
|
||||
import prisma from '../prisma/client'
|
||||
import prisma from '../prisma/client.ts'
|
||||
|
||||
export const auth = new Lucia<object, { username: string, displayName: string }>(new PrismaAdapter(prisma.session, prisma.user))
|
||||
declare module 'lucia' {
|
||||
interface Register {
|
||||
Lucia: typeof Lucia
|
||||
UserId: string
|
||||
DatabaseUserAttributes: DatabaseUserAttributes
|
||||
}
|
||||
}
|
||||
|
||||
interface DatabaseUserAttributes {
|
||||
id: string
|
||||
displayName: string
|
||||
username: string
|
||||
}
|
||||
|
||||
export const auth = new Lucia(new PrismaAdapter(prisma.session, prisma.user), {
|
||||
getUserAttributes: ({ id, displayName, username }) => {
|
||||
return {
|
||||
id,
|
||||
displayName,
|
||||
username,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export type Auth = typeof auth
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
"start": "ts-node --transpile-only server.ts",
|
||||
"db:deploy": "npx prisma migrate deploy && npx prisma generate"
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "yarn@4.10.3",
|
||||
"dependencies": {
|
||||
"@fastify/autoload": "^6.3.1",
|
||||
"@fastify/cookie": "^11.0.2",
|
||||
"@fastify/cors": "^11.1.0",
|
||||
"@lucia-auth/adapter-prisma": "^4.0.1",
|
||||
"@prisma/client": "^6.17.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"consola": "^3.4.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.1.0",
|
||||
"fastify": "^5.6.1",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
"lucia": "^3.2.2",
|
||||
"mediasoup": "^3.19.3",
|
||||
"prisma": "^6.17.0",
|
||||
@@ -25,8 +27,6 @@
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@types/bcrypt": "^6",
|
||||
"@types/cookie-parser": "^1",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/ws": "^8",
|
||||
"eslint": "^9.36.0",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
40
server/plugins/auth.ts
Normal file
40
server/plugins/auth.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { Session, User } from 'lucia'
|
||||
import fp from 'fastify-plugin'
|
||||
import { auth } from '../auth/lucia.ts'
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyRequest {
|
||||
user: User | null
|
||||
session: Session | null
|
||||
}
|
||||
}
|
||||
|
||||
export default fp(async (fastify) => {
|
||||
fastify.decorateRequest('user', null)
|
||||
fastify.decorateRequest('session', null)
|
||||
|
||||
fastify.addHook('preHandler', async (req, reply) => {
|
||||
try {
|
||||
const sessionId = auth.readSessionCookie(req.headers.cookie ?? '')
|
||||
|
||||
const { session, user } = await auth.validateSession(sessionId ?? '')
|
||||
|
||||
if (session && session.fresh) {
|
||||
const cookie = auth.createSessionCookie(session.id)
|
||||
reply.setCookie(cookie.name, cookie.value, cookie.attributes)
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
const blank = auth.createBlankSessionCookie()
|
||||
reply.setCookie(blank.name, blank.value, blank.attributes)
|
||||
}
|
||||
|
||||
req.user = user
|
||||
req.session = session
|
||||
}
|
||||
catch {
|
||||
req.user = null
|
||||
req.session = null
|
||||
}
|
||||
})
|
||||
})
|
||||
29
server/plugins/mediasoup-router.ts
Normal file
29
server/plugins/mediasoup-router.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type * as mediasoup from 'mediasoup'
|
||||
import fp from 'fastify-plugin'
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
mediasoupRouter: mediasoup.types.Router
|
||||
}
|
||||
}
|
||||
|
||||
export default fp<mediasoup.types.RouterOptions>(
|
||||
async (fastify, opts) => {
|
||||
const router = await fastify.mediasoupWorker.createRouter(opts)
|
||||
|
||||
fastify.decorate('mediasoupRouter', router)
|
||||
},
|
||||
{ name: 'mediasoup-router', dependencies: ['mediasoup-worker'] },
|
||||
)
|
||||
|
||||
export const autoConfig: mediasoup.types.RouterOptions = {
|
||||
mediaCodecs: [
|
||||
{
|
||||
kind: 'audio',
|
||||
mimeType: 'audio/opus',
|
||||
clockRate: 48000,
|
||||
channels: 2,
|
||||
parameters: { useinbandfec: 1, stereo: 1 },
|
||||
},
|
||||
],
|
||||
}
|
||||
23
server/plugins/mediasoup-worker.ts
Normal file
23
server/plugins/mediasoup-worker.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { consola } from 'consola'
|
||||
import fp from 'fastify-plugin'
|
||||
import * as mediasoup from 'mediasoup'
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
mediasoupWorker: mediasoup.types.Worker
|
||||
}
|
||||
}
|
||||
|
||||
export default fp(
|
||||
async (fastify) => {
|
||||
const worker = await mediasoup.createWorker()
|
||||
worker.on('died', () => {
|
||||
consola.error('[Mediasoup]', 'Worker died, exiting...')
|
||||
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
fastify.decorate('mediasoupWorker', worker)
|
||||
},
|
||||
{ name: 'mediasoup-worker' },
|
||||
)
|
||||
39
server/plugins/socket.ts
Normal file
39
server/plugins/socket.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import type { ServerOptions } from 'socket.io'
|
||||
import fp from 'fastify-plugin'
|
||||
import { Server } from 'socket.io'
|
||||
import registerWebrtcSocket from '../socket/webrtc.ts'
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
io: Server
|
||||
}
|
||||
}
|
||||
|
||||
export default fp<Partial<ServerOptions>>(
|
||||
async (fastify, opts) => {
|
||||
fastify.decorate('io', new Server(fastify.server, opts))
|
||||
|
||||
fastify.addHook('preClose', () => {
|
||||
fastify.io.disconnectSockets(true)
|
||||
})
|
||||
|
||||
fastify.addHook('onClose', async (fastify: FastifyInstance) => {
|
||||
await fastify.io.close()
|
||||
})
|
||||
|
||||
fastify.ready(() => {
|
||||
registerWebrtcSocket(fastify.io, fastify.mediasoupRouter)
|
||||
})
|
||||
},
|
||||
{ name: 'socket-io', dependencies: ['mediasoup-worker', 'mediasoup-router'] },
|
||||
)
|
||||
|
||||
export const autoConfig: Partial<ServerOptions> = {
|
||||
path: '/chad/ws',
|
||||
cors: {
|
||||
origin: process.env.CORS_ORIGIN || '*',
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true,
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const instance = new PrismaClient({
|
||||
const client = new PrismaClient({
|
||||
log: ['query', 'error', 'warn'],
|
||||
})
|
||||
|
||||
export default instance
|
||||
export default client
|
||||
|
||||
@@ -5,6 +5,7 @@ datasource db {
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
// output = "./generated/client"
|
||||
}
|
||||
|
||||
model User {
|
||||
|
||||
120
server/routes/auth.ts
Normal file
120
server/routes/auth.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { z } from 'zod'
|
||||
import { auth } from '../auth/lucia.ts'
|
||||
import prisma from '../prisma/client.ts'
|
||||
|
||||
export default function (fastify: FastifyInstance) {
|
||||
fastify.post('/register', async (req, reply) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(6),
|
||||
})
|
||||
const input = schema.parse(req.body)
|
||||
|
||||
const hashed = await bcrypt.hash(input.password, 10)
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: input.username,
|
||||
password: hashed,
|
||||
displayName: input.username,
|
||||
},
|
||||
})
|
||||
|
||||
const session = await auth.createSession(user.id, {})
|
||||
const cookie = auth.createSessionCookie(session.id)
|
||||
|
||||
reply.setCookie(cookie.name, cookie.value, cookie.attributes)
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
fastify.log.error(err)
|
||||
reply.code(400)
|
||||
|
||||
if (err instanceof z.ZodError) {
|
||||
reply.send({ error: z.prettifyError(err) })
|
||||
}
|
||||
else {
|
||||
reply.send({ error: err.message })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fastify.post('/login', async (req, reply) => {
|
||||
try {
|
||||
const schema = z.object({
|
||||
username: z.string().min(1),
|
||||
password: z.string(),
|
||||
})
|
||||
const input = schema.parse(req.body)
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { username: input.username },
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
return reply.code(404).send({ error: 'Incorrect username or password' })
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(input.password, user.password)
|
||||
if (!validPassword) {
|
||||
return reply.code(404).send({ error: 'Incorrect username or password' })
|
||||
}
|
||||
|
||||
const session = await auth.createSession(user.id, {})
|
||||
const cookie = auth.createSessionCookie(session.id)
|
||||
|
||||
cookie.attributes.secure = false
|
||||
|
||||
reply.setCookie(cookie.name, cookie.value, cookie.attributes)
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
displayName: user.displayName,
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
fastify.log.error(err)
|
||||
reply.code(400)
|
||||
|
||||
if (err instanceof z.ZodError) {
|
||||
reply.send({ error: z.prettifyError(err) })
|
||||
}
|
||||
else {
|
||||
reply.send({ error: err.message })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/me', async (req, reply) => {
|
||||
if (req.user) {
|
||||
return req.user
|
||||
}
|
||||
|
||||
reply.code(401).send(false)
|
||||
})
|
||||
|
||||
fastify.post('/logout', async (req, reply) => {
|
||||
try {
|
||||
if (req.session)
|
||||
await auth.invalidateSession(req.session.id)
|
||||
|
||||
const blank = auth.createBlankSessionCookie()
|
||||
|
||||
reply.setCookie(blank.name, blank.value, blank.attributes)
|
||||
|
||||
return true
|
||||
}
|
||||
catch (err) {
|
||||
fastify.log.error(err)
|
||||
reply.code(400).send({ error: err.message })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,61 +1,41 @@
|
||||
import { createServer as createHttpServer } from 'node:http'
|
||||
import { createExpressMiddleware } from '@trpc/server/adapters/express'
|
||||
import { consola } from 'consola'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import cors from 'cors'
|
||||
import express from 'express'
|
||||
import * as mediasoup from 'mediasoup'
|
||||
import { Server as SocketServer } from 'socket.io'
|
||||
import { createContext } from './trpc/context'
|
||||
import { appRouter } from './trpc/routers'
|
||||
import webrtcSocket from './webrtc/socket'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import FastifyAutoLoad from '@fastify/autoload'
|
||||
import FastifyCookie from '@fastify/cookie'
|
||||
import FastifyCors from '@fastify/cors'
|
||||
import Fastify from 'fastify'
|
||||
import prisma from './prisma/client.ts'
|
||||
|
||||
(async () => {
|
||||
const app = express()
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
app.use(cors())
|
||||
app.use(cookieParser())
|
||||
app.use(express.json())
|
||||
const fastify = Fastify({
|
||||
logger: true,
|
||||
})
|
||||
|
||||
app.use(
|
||||
'/chad/trpc',
|
||||
createExpressMiddleware({
|
||||
router: appRouter,
|
||||
createContext,
|
||||
}),
|
||||
)
|
||||
fastify.register(FastifyCors)
|
||||
|
||||
const server = createHttpServer(app)
|
||||
fastify.register(FastifyCookie)
|
||||
|
||||
const worker = await mediasoup.createWorker()
|
||||
worker.on('died', () => {
|
||||
consola.error('[Mediasoup]', 'Worker died, exiting...')
|
||||
fastify.register(FastifyAutoLoad, {
|
||||
dir: join(__dirname, 'plugins'),
|
||||
})
|
||||
|
||||
fastify.register(FastifyAutoLoad, {
|
||||
dir: join(__dirname, 'routes'),
|
||||
})
|
||||
|
||||
;(async () => {
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 4000
|
||||
|
||||
try {
|
||||
await fastify.listen({ port })
|
||||
|
||||
await prisma.$connect()
|
||||
fastify.log.info('Testing DB Connection. OK')
|
||||
}
|
||||
catch (err) {
|
||||
fastify.log.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
const router = await worker.createRouter({
|
||||
mediaCodecs: [
|
||||
{
|
||||
kind: 'audio',
|
||||
mimeType: 'audio/opus',
|
||||
clockRate: 48000,
|
||||
channels: 2,
|
||||
parameters: { useinbandfec: 1, stereo: 1 },
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const io = new SocketServer(server, {
|
||||
path: '/chad/ws',
|
||||
cors: {
|
||||
origin: process.env.CORS_ORIGIN || '*',
|
||||
},
|
||||
})
|
||||
|
||||
webrtcSocket(io, router)
|
||||
|
||||
server.listen(process.env.PORT || 4000, () => {
|
||||
console.log('✅ Server running')
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { User } from '@prisma/client'
|
||||
import type { types } from 'mediasoup'
|
||||
import type { Namespace, RemoteSocket, Socket, Server as SocketServer } from 'socket.io'
|
||||
import { consola } from 'consola'
|
||||
import prisma from '../prisma/client.ts'
|
||||
|
||||
interface ChadClient {
|
||||
id: string
|
||||
username: string
|
||||
socketId: string
|
||||
userId: User['id']
|
||||
username: User['username']
|
||||
displayName: User['displayName']
|
||||
inputMuted: boolean
|
||||
outputMuted: boolean
|
||||
}
|
||||
@@ -27,10 +31,9 @@ type EventCallback<T = SuccessCallbackResult> = (result: T | ErrorCallbackResult
|
||||
interface ClientToServerEvents {
|
||||
join: (
|
||||
options: {
|
||||
username: string
|
||||
rtpCapabilities: types.RtpCapabilities
|
||||
},
|
||||
cb: EventCallback<{ id: string, username: string }[]>
|
||||
cb: EventCallback<ChadClient[]>
|
||||
) => void
|
||||
getRtpCapabilities: (
|
||||
cb: EventCallback<types.RtpCapabilities>
|
||||
@@ -88,12 +91,13 @@ interface ClientToServerEvents {
|
||||
cb: EventCallback
|
||||
) => void
|
||||
updateClient: (
|
||||
options: Partial<Omit<ChadClient, 'id'>>,
|
||||
cb: EventCallback
|
||||
options: Partial<Omit<ChadClient, 'socketId' | 'userId'>>,
|
||||
cb: EventCallback<ChadClient>
|
||||
) => void
|
||||
}
|
||||
|
||||
interface ServerToClientEvents {
|
||||
authenticated: () => void
|
||||
newPeer: (arg: ChadClient) => void
|
||||
producers: (arg: ProducerShort[]) => void
|
||||
newConsumer: (
|
||||
@@ -114,14 +118,16 @@ interface ServerToClientEvents {
|
||||
consumerPaused: (arg: { consumerId: string }) => void
|
||||
consumerResumed: (arg: { consumerId: string }) => void
|
||||
consumerScore: (arg: { consumerId: string, score: types.ConsumerScore }) => void
|
||||
clientChanged: (clientId: ChadClient['id'], client: ChadClient) => void
|
||||
clientChanged: (clientId: ChadClient['socketId'], client: ChadClient) => void
|
||||
}
|
||||
|
||||
interface InterServerEvent {}
|
||||
|
||||
interface SocketData {
|
||||
joined: boolean
|
||||
username: string
|
||||
userId: User['id']
|
||||
username: User['username']
|
||||
displayName: User['displayName']
|
||||
inputMuted: boolean
|
||||
outputMuted: boolean
|
||||
rtpCapabilities: types.RtpCapabilities
|
||||
@@ -130,6 +136,8 @@ interface SocketData {
|
||||
consumers: Map<types.Consumer['id'], types.Consumer>
|
||||
}
|
||||
|
||||
type SomeSocket = Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> | RemoteSocket<ServerToClientEvents, SocketData>
|
||||
|
||||
export default function (io: SocketServer, router: types.Router) {
|
||||
const namespace: Namespace<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData> = io.of('/webrtc')
|
||||
|
||||
@@ -138,7 +146,6 @@ export default function (io: SocketServer, router: types.Router) {
|
||||
|
||||
socket.data.joined = false
|
||||
|
||||
socket.data.username = socket.id
|
||||
socket.data.inputMuted = false
|
||||
socket.data.outputMuted = false
|
||||
|
||||
@@ -146,24 +153,35 @@ export default function (io: SocketServer, router: types.Router) {
|
||||
socket.data.producers = new Map()
|
||||
socket.data.consumers = new Map()
|
||||
|
||||
socket.on('join', async ({ username, rtpCapabilities }, cb) => {
|
||||
prisma.user.findUnique({
|
||||
where: {
|
||||
id: socket.handshake.auth.userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
displayName: true,
|
||||
},
|
||||
}).then(({ id, username, displayName }) => {
|
||||
socket.data.userId = id
|
||||
socket.data.username = username
|
||||
socket.data.displayName = displayName
|
||||
|
||||
socket.emit('authenticated')
|
||||
})
|
||||
|
||||
socket.on('join', async ({ rtpCapabilities }, cb) => {
|
||||
if (socket.data.joined) {
|
||||
consola.error('[WebRtc]', 'Already joined')
|
||||
cb({ error: 'Already joined' })
|
||||
}
|
||||
|
||||
socket.data.joined = true
|
||||
socket.data.username = username
|
||||
socket.data.rtpCapabilities = rtpCapabilities
|
||||
|
||||
const joinedSockets = await getJoinedSockets()
|
||||
|
||||
cb(joinedSockets.map((s) => {
|
||||
return {
|
||||
id: s.id,
|
||||
username: s.data.username,
|
||||
}
|
||||
}))
|
||||
cb(joinedSockets.map(socketToClient))
|
||||
|
||||
for (const joinedSocket of joinedSockets.filter(joinedSocket => joinedSocket.id !== socket.id)) {
|
||||
for (const producer of joinedSocket.data.producers.values()) {
|
||||
@@ -417,9 +435,18 @@ export default function (io: SocketServer, router: types.Router) {
|
||||
cb({ ok: true })
|
||||
})
|
||||
|
||||
socket.on('updateClient', (updatedClient, cb) => {
|
||||
if (updatedClient.username) {
|
||||
socket.data.username = updatedClient.username
|
||||
socket.on('updateClient', async (updatedClient, cb) => {
|
||||
if (updatedClient.displayName) {
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: socket.data.userId,
|
||||
},
|
||||
data: {
|
||||
displayName: updatedClient.displayName,
|
||||
},
|
||||
})
|
||||
|
||||
socket.data.displayName = updatedClient.displayName
|
||||
}
|
||||
|
||||
if (updatedClient.inputMuted) {
|
||||
@@ -430,7 +457,7 @@ export default function (io: SocketServer, router: types.Router) {
|
||||
socket.data.outputMuted = updatedClient.outputMuted
|
||||
}
|
||||
|
||||
cb({ ok: true })
|
||||
cb(socketToClient(socket))
|
||||
|
||||
namespace.emit('clientChanged', socket.id, socketToClient(socket))
|
||||
})
|
||||
@@ -455,8 +482,8 @@ export default function (io: SocketServer, router: types.Router) {
|
||||
}
|
||||
|
||||
async function createConsumer(
|
||||
consumerSocket: Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>,
|
||||
producerSocket: RemoteSocket<ServerToClientEvents, SocketData>,
|
||||
consumerSocket: SomeSocket,
|
||||
producerSocket: SomeSocket,
|
||||
producer: types.Producer,
|
||||
) {
|
||||
if (
|
||||
@@ -554,10 +581,12 @@ export default function (io: SocketServer, router: types.Router) {
|
||||
}
|
||||
}
|
||||
|
||||
function socketToClient(socket: Socket<ClientToServerEvents, ServerToClientEvents, InterServerEvent, SocketData>): ChadClient {
|
||||
function socketToClient(socket: SomeSocket): ChadClient {
|
||||
return {
|
||||
id: socket.id,
|
||||
socketId: socket.id,
|
||||
userId: socket.data.userId,
|
||||
username: socket.data.username,
|
||||
displayName: socket.data.displayName,
|
||||
inputMuted: socket.data.inputMuted,
|
||||
outputMuted: socket.data.outputMuted,
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express'
|
||||
import { auth } from '../auth/lucia'
|
||||
|
||||
export async function createContext({ res, req }: CreateExpressContextOptions) {
|
||||
const sessionId = auth.readSessionCookie(req.headers.cookie ?? '')
|
||||
|
||||
if (!sessionId)
|
||||
return { res, req }
|
||||
|
||||
const { session, user } = await auth.validateSession(sessionId)
|
||||
|
||||
if (session && session.fresh) {
|
||||
res.appendHeader('Set-Cookie', auth.createSessionCookie(session.id).serialize())
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
res.appendHeader('Set-Cookie', auth.createBlankSessionCookie().serialize())
|
||||
}
|
||||
|
||||
return { res, req, session, user }
|
||||
}
|
||||
|
||||
export type Context = Awaited<ReturnType<typeof createContext>>
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { Context } from './context'
|
||||
import { initTRPC } from '@trpc/server'
|
||||
|
||||
const t = initTRPC.context<Context>().create()
|
||||
|
||||
export const router = t.router
|
||||
|
||||
export const publicProcedure = t.procedure
|
||||
|
||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
||||
if (!ctx.session?.fresh)
|
||||
throw new Error('UNAUTHORIZED')
|
||||
|
||||
return next({ ctx: { ...ctx } })
|
||||
})
|
||||
@@ -1,74 +0,0 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import bcrypt from 'bcrypt'
|
||||
import { z } from 'zod'
|
||||
import { auth } from '../../auth/lucia'
|
||||
import client from '../../prisma/client'
|
||||
import { protectedProcedure, publicProcedure, router } from '../router'
|
||||
|
||||
export const authRouter = router({
|
||||
register: publicProcedure
|
||||
.input(z.object({ username: z.string().min(1), password: z.string().min(6) }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const hashed = await bcrypt.hash(input.password, 10)
|
||||
const user = await client.user.create({
|
||||
data: {
|
||||
username: input.username,
|
||||
password: hashed,
|
||||
displayName: input.username,
|
||||
},
|
||||
})
|
||||
|
||||
const session = await auth.createSession(user.id, {})
|
||||
const cookie = auth.createSessionCookie(session.id)
|
||||
|
||||
ctx.res.setHeader('Set-Cookie', cookie.serialize())
|
||||
|
||||
return { user }
|
||||
}),
|
||||
|
||||
login: publicProcedure
|
||||
.input(z.object({ username: z.string().min(1), password: z.string() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const user = await client.user.findFirst({
|
||||
where: {
|
||||
username: input.username,
|
||||
},
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Incorrect username or password',
|
||||
})
|
||||
}
|
||||
|
||||
const validPassword = await bcrypt.compare(input.password, user.password)
|
||||
|
||||
if (!validPassword) {
|
||||
throw new TRPCError({
|
||||
code: 'NOT_FOUND',
|
||||
message: 'Incorrect username or password',
|
||||
})
|
||||
}
|
||||
|
||||
const session = await auth.createSession(user.id, {})
|
||||
const cookie = auth.createSessionCookie(session.id)
|
||||
|
||||
ctx.res.setHeader('Set-Cookie', cookie.serialize())
|
||||
|
||||
return { user }
|
||||
}),
|
||||
|
||||
me: protectedProcedure.query(({ ctx }) => {
|
||||
return ctx.user
|
||||
}),
|
||||
|
||||
logout: publicProcedure.mutation(async ({ ctx }) => {
|
||||
if (ctx.session)
|
||||
await auth.invalidateSession(ctx.session.id)
|
||||
|
||||
ctx.res.setHeader('Set-Cookie', auth.createBlankSessionCookie().serialize())
|
||||
|
||||
return true
|
||||
}),
|
||||
})
|
||||
@@ -1,10 +0,0 @@
|
||||
import { router } from '../router'
|
||||
import { authRouter } from './auth'
|
||||
// import { webrtcRouter } from './webrtc'
|
||||
|
||||
export const appRouter = router({
|
||||
auth: authRouter,
|
||||
// webrtc: webrtcRouter,
|
||||
})
|
||||
|
||||
export type AppRouter = typeof appRouter
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "nodenext",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"allowImportingTsExtensions": true
|
||||
}
|
||||
}
|
||||
|
||||
1076
server/yarn.lock
1076
server/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user