mirror of
https://github.com/jeffvli/sonixd.git
synced 2026-04-29 10:42:40 -04:00
Fix session auth
Add custom session secret Set session to rolling 30 day expiry 10 minute check Custom cookie name
This commit is contained in:
@@ -1,31 +1,28 @@
|
||||
import bcrypt from 'bcryptjs';
|
||||
import passport, { PassportStatic } from 'passport';
|
||||
import passport from 'passport';
|
||||
import { Strategy } from 'passport-local';
|
||||
|
||||
import orm from './prisma';
|
||||
import prisma from './prisma';
|
||||
|
||||
const LocalStrategy = require('passport-local');
|
||||
passport.use(
|
||||
new Strategy(async (username: string, password: string, done: any) => {
|
||||
const user = await prisma.user.findUnique({ where: { username } });
|
||||
|
||||
export = (p: PassportStatic) => {
|
||||
p.use(
|
||||
new LocalStrategy(async (username: string, password: string, done: any) => {
|
||||
const user = await orm.user.findUnique({ where: { username } });
|
||||
if (user === null || user === undefined) {
|
||||
return done(null, false);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return done(null, false);
|
||||
}
|
||||
if (!user.enabled) {
|
||||
return done(null, false, { message: 'The user is not enabled.' });
|
||||
}
|
||||
|
||||
if (!user.enabled) {
|
||||
return done(null, false, { message: 'The user is not enabled.' });
|
||||
}
|
||||
if (await bcrypt.compare(password, user.password)) {
|
||||
return done(null, user);
|
||||
}
|
||||
|
||||
if (await bcrypt.compare(password, user.password)) {
|
||||
return done(null, user);
|
||||
}
|
||||
|
||||
return done(null, false, { message: 'Invalid credentials.' });
|
||||
})
|
||||
);
|
||||
};
|
||||
return done(null, false, { message: 'Invalid credentials.' });
|
||||
})
|
||||
);
|
||||
|
||||
passport.serializeUser((user: any, done) => {
|
||||
return done(null, user.id);
|
||||
@@ -34,7 +31,7 @@ passport.serializeUser((user: any, done) => {
|
||||
passport.deserializeUser(async (id: number, done) => {
|
||||
return done(
|
||||
null,
|
||||
await orm.user.findUnique({
|
||||
await prisma.user.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
|
||||
35
src/server/middleware/authenticateLocal.ts
Normal file
35
src/server/middleware/authenticateLocal.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import passport from 'passport';
|
||||
|
||||
const authenticateLocal = (req: Request, res: Response, next: NextFunction) => {
|
||||
passport.authenticate('local', { session: true }, (err, _user, info) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
statusCode: 401,
|
||||
response: 'Error',
|
||||
error: {
|
||||
message: info?.message || 'Invalid authorization.',
|
||||
path: req.path,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const u: any = req.user;
|
||||
|
||||
req.user = {
|
||||
id: u?.id,
|
||||
username: u?.username,
|
||||
createdAt: u?.createdAt,
|
||||
updatedAt: u?.updatedAt,
|
||||
enabled: u?.enabled,
|
||||
};
|
||||
|
||||
return next();
|
||||
})(req, res, next);
|
||||
};
|
||||
|
||||
export default authenticateLocal;
|
||||
@@ -1 +1,2 @@
|
||||
export { default as errorHandler } from './error-handler';
|
||||
export { default as authenticateLocal } from './authenticateLocal';
|
||||
|
||||
@@ -15,17 +15,8 @@ CREATE TABLE "User" (
|
||||
"password" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserRole" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"server" INTEGER NOT NULL,
|
||||
"playlist" INTEGER NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
@@ -155,9 +146,6 @@ CREATE UNIQUE INDEX "Session.sid_unique" ON "Session"("sid");
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User.username_unique" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserRole.userId_unique" ON "UserRole"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Server.url_unique" ON "Server"("url");
|
||||
|
||||
@@ -173,9 +161,6 @@ CREATE UNIQUE INDEX "Album.serverId_remoteId_unique" ON "Album"("serverId", "rem
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Song.serverId_remoteId_unique" ON "Song"("serverId", "remoteId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserRole" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Server" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -20,20 +20,11 @@ model User {
|
||||
password String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
enabled Boolean
|
||||
enabled Boolean @default(false)
|
||||
isAdmin Boolean @default(false)
|
||||
|
||||
servers Server[]
|
||||
tasks Task[]
|
||||
userRole UserRole?
|
||||
}
|
||||
|
||||
model UserRole {
|
||||
id Int @id @default(autoincrement())
|
||||
server Int
|
||||
playlist Int
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int @unique
|
||||
servers Server[]
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model Server {
|
||||
|
||||
@@ -14,6 +14,7 @@ async function main() {
|
||||
username: 'admin',
|
||||
password: hashedPassword,
|
||||
enabled: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import express, { Router } from 'express';
|
||||
import passport from 'passport';
|
||||
|
||||
import { authenticateLocal } from '../middleware';
|
||||
import { authService } from '../services';
|
||||
import { getSuccessResponse } from '../utils';
|
||||
|
||||
@@ -10,7 +11,7 @@ authRouter.post('/login', passport.authenticate('local'), async (req, res) => {
|
||||
const { username } = req.body;
|
||||
const { statusCode, data } = await authService.login({ username });
|
||||
|
||||
res.status(statusCode).json(getSuccessResponse({ statusCode, data }));
|
||||
return res.status(statusCode).json(getSuccessResponse({ statusCode, data }));
|
||||
});
|
||||
|
||||
authRouter.post('/register', async (req, res) => {
|
||||
@@ -23,4 +24,12 @@ authRouter.post('/register', async (req, res) => {
|
||||
return res.status(statusCode).json(getSuccessResponse({ statusCode, data }));
|
||||
});
|
||||
|
||||
authRouter.post('/logout', authenticateLocal, async (req, res) => {
|
||||
req.session.destroy(() => {
|
||||
res.redirect('/login');
|
||||
});
|
||||
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
|
||||
export default authRouter;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import express, { Router } from 'express';
|
||||
|
||||
import orm from '../lib/prisma';
|
||||
import { authenticateLocal } from '../middleware';
|
||||
|
||||
const usersRouter: Router = express.Router();
|
||||
|
||||
usersRouter.get('/', async (_req, res) => {
|
||||
usersRouter.get('/', authenticateLocal, async (_req, res) => {
|
||||
const users = await orm.user.findMany();
|
||||
|
||||
return res.status(200).json(users);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from 'path';
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { PrismaSessionStore } from '@quixo3/prisma-session-store';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
@@ -9,32 +10,18 @@ import passport from 'passport';
|
||||
|
||||
import 'express-async-errors';
|
||||
|
||||
import orm from './lib/prisma';
|
||||
import { errorHandler } from './middleware';
|
||||
import routes from './routes';
|
||||
|
||||
require('./lib/passport');
|
||||
|
||||
const PORT = 9321;
|
||||
|
||||
const app = express();
|
||||
|
||||
app.set('trust proxy', 1);
|
||||
const staticPath = path.join(__dirname, '../sonixd-client/');
|
||||
|
||||
app.use(express.static(staticPath));
|
||||
app.use(
|
||||
session({
|
||||
secret: 'secret',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
store: new PrismaSessionStore(orm, {
|
||||
checkPeriod: 2 * 60 * 1000,
|
||||
dbRecordIdIsSessionId: true,
|
||||
dbRecordIdFunction: undefined,
|
||||
}),
|
||||
cookie: {
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
cors({
|
||||
origin: [`http://localhost:4343`, `${process.env.APP_BASE_URL}`],
|
||||
@@ -44,10 +31,27 @@ app.use(
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser('secret'));
|
||||
app.use(cookieParser());
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.DB_SECRET || 'secret',
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
rolling: true,
|
||||
name: 'user_session',
|
||||
cookie: {
|
||||
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
|
||||
},
|
||||
store: new PrismaSessionStore(new PrismaClient(), {
|
||||
checkPeriod: 10 * 60 * 1000, // 10 minutes
|
||||
dbRecordIdIsSessionId: true,
|
||||
dbRecordIdFunction: undefined,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
require('./lib/passport')(passport);
|
||||
|
||||
app.get('/', (_req, res) => {
|
||||
res.sendFile(path.join(staticPath, 'index.html'));
|
||||
|
||||
@@ -12,10 +12,26 @@ class ApiError extends Error {
|
||||
return new ApiError({ statusCode: 400, message });
|
||||
}
|
||||
|
||||
static unauthorized(message: string) {
|
||||
return new ApiError({ statusCode: 401, message });
|
||||
}
|
||||
|
||||
static forbidden(message: string) {
|
||||
return new ApiError({ statusCode: 403, message });
|
||||
}
|
||||
|
||||
static notFound(message: string) {
|
||||
return new ApiError({ statusCode: 404, message });
|
||||
}
|
||||
|
||||
static conflict(message: string) {
|
||||
return new ApiError({ statusCode: 409, message });
|
||||
}
|
||||
|
||||
static gone(message: string) {
|
||||
return new ApiError({ statusCode: 410, message });
|
||||
}
|
||||
|
||||
static internal(message: string) {
|
||||
return new ApiError({ statusCode: 500, message });
|
||||
}
|
||||
|
||||
10
src/types.ts
10
src/types.ts
@@ -220,3 +220,13 @@ export interface Pagination {
|
||||
serverSide?: boolean;
|
||||
recordsPerPage: number;
|
||||
}
|
||||
|
||||
export type UserResponse = {
|
||||
id: number;
|
||||
username: string;
|
||||
password?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
enabled: boolean;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user