mirror of
https://github.com/KeyZox71/knl_meowscendence.git
synced 2026-01-01 06:06:41 +01:00
「✨」 feat: added 2fa (closes #21)
This commit is contained in:
@ -7,8 +7,12 @@ import { gRedir } from './gRedir.js';
|
||||
import authDB from '../../utils/authDB.js'
|
||||
import { gLogCallback } from './gLogCallback.js';
|
||||
import { gRegisterCallback } from './gRegisterCallback.js';
|
||||
import { totpSetup } from './totpSetup.js';
|
||||
import { totpDelete } from './totpDelete.js';
|
||||
import { totpVerify } from './totpVerify.js';
|
||||
|
||||
const saltRounds = 10;
|
||||
export const appName = process.env.APP_NAME || 'knl_meowscendence';
|
||||
|
||||
authDB.prepareDB();
|
||||
|
||||
@ -27,17 +31,20 @@ export default async function(fastify, options) {
|
||||
}
|
||||
});
|
||||
fastify.register(fastifyCookie);
|
||||
|
||||
fastify.get('/me', async (request, reply) => {
|
||||
fastify.decorate("authenticate", async function(request, reply) {
|
||||
try {
|
||||
const token = request.cookies.token;
|
||||
const decoded = await fastify.jwt.verify(token);
|
||||
return { user: decoded.user };
|
||||
} catch {
|
||||
return reply.code(401).send({ error: 'Unauthorized' });
|
||||
const jwt = await request.jwtVerify();
|
||||
request.user = jwt.user;
|
||||
} catch (err) {
|
||||
reply.code(401).send({ error: 'Unauthorized' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
fastify.get('/me', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return { user: request.user };
|
||||
});
|
||||
|
||||
// GOOGLE sign in
|
||||
fastify.get('/login/google', async (request, reply) => {
|
||||
return gRedir(request, reply, fastify, '/login/google/callback');
|
||||
@ -47,10 +54,32 @@ export default async function(fastify, options) {
|
||||
});
|
||||
fastify.get('/login/google/callback', async (request, reply) => {
|
||||
return gLogCallback(request, reply, fastify);
|
||||
})
|
||||
});
|
||||
fastify.get('/register/google/callback', async (request, reply) => {
|
||||
return gRegisterCallback(request, reply, fastify);
|
||||
})
|
||||
});
|
||||
|
||||
// TOTP
|
||||
fastify.post('/2fa', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return totpSetup(request, reply, fastify);
|
||||
});
|
||||
fastify.post('/2fa/verify', {
|
||||
preHandler: [fastify.authenticate], schema: {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['token'],
|
||||
properties: {
|
||||
token: { type: 'string', minLength: 6, maxLength: 6 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}, async (request, reply) => {
|
||||
return totpVerify(request, reply, fastify);
|
||||
});
|
||||
fastify.delete('/2fa', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return totpDelete(request, reply, fastify);
|
||||
});
|
||||
|
||||
|
||||
fastify.post('/login', {
|
||||
schema: {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
import authDB from '../../utils/authDB.js';
|
||||
import { verifyTOTP } from "../../utils/totp.js";
|
||||
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
|
||||
@ -34,6 +35,17 @@ export async function login(request, reply, fastify) {
|
||||
return reply.code(401).send({ error: "Incorrect password" });
|
||||
}
|
||||
|
||||
const userTOTP = authDB.getUser(user);
|
||||
if (userTOTP.totpEnabled == 1) {
|
||||
if (!request.body.token){
|
||||
return reply.code(401).send({ error: 'Invalid 2FA token' });
|
||||
}
|
||||
const isValid = verifyTOTP(userTOTP.totpHash, request.body.token);
|
||||
if (!isValid) {
|
||||
return reply.code(401).send({ error: 'Invalid 2FA token' });
|
||||
}
|
||||
}
|
||||
|
||||
const token = fastify.jwt.sign({ user });
|
||||
|
||||
return reply
|
||||
|
||||
@ -46,7 +46,7 @@ export async function register(request, reply, saltRounds, fastify) {
|
||||
sameSite: 'lax',
|
||||
})
|
||||
.code(200)
|
||||
.send({ msg: 'Register successfuly' });
|
||||
.send({ msg: 'Register successfully' });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
|
||||
18
src/api/auth/totpDelete.js
Normal file
18
src/api/auth/totpDelete.js
Normal file
@ -0,0 +1,18 @@
|
||||
import authDB from "../../utils/authDB.js";
|
||||
|
||||
/**
|
||||
* @param {import("fastify").FastifyRequest} request
|
||||
* @param {import("fastify").FastifyReply} reply
|
||||
* @param {import("fastify").FastifyInstance} fastify
|
||||
*
|
||||
* @returns {import('fastify').FastifyReply}
|
||||
*/
|
||||
export function totpDelete(request, reply, fastify) {
|
||||
try {
|
||||
authDB.disableTOTP(request.user);
|
||||
return reply.code(200).send({ msg: 'TOTP removed' });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
}
|
||||
34
src/api/auth/totpSetup.js
Normal file
34
src/api/auth/totpSetup.js
Normal file
@ -0,0 +1,34 @@
|
||||
import authDB from '../../utils/authDB.js';
|
||||
import { appName } from './default.js';
|
||||
import { generateRandomSecret } from '../../utils/totp.js';
|
||||
|
||||
/**
|
||||
* @param {import("fastify").FastifyRequest} request
|
||||
* @param {import("fastify").FastifyReply} reply
|
||||
* @param {import("fastify").FastifyInstance} fastify
|
||||
*
|
||||
* @returns {import('fastify').FastifyReply}
|
||||
*/
|
||||
export async function totpSetup(request, reply, fastify) {
|
||||
try {
|
||||
const username = request.user;
|
||||
|
||||
if (!authDB.checkUser(username)) {
|
||||
return reply.code(404).send({ error: "User not found" });
|
||||
}
|
||||
|
||||
const secret = generateRandomSecret();
|
||||
|
||||
const otpauthUrl = `otpauth://totp/${encodeURI(appName)}:${encodeURI(username)}?secret=${secret}&issuer=${encodeURI(appName)}`;
|
||||
|
||||
authDB.setTOTPSecret(username, secret);
|
||||
|
||||
return reply.send({
|
||||
secret,
|
||||
otpauthUrl
|
||||
});
|
||||
} catch (error) {
|
||||
fastify.log.error(error);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
}
|
||||
34
src/api/auth/totpVerify.js
Normal file
34
src/api/auth/totpVerify.js
Normal file
@ -0,0 +1,34 @@
|
||||
import authDB from "../../utils/authDB.js";
|
||||
import { verifyTOTP } from "../../utils/totp.js";
|
||||
|
||||
/**
|
||||
* @param {import("fastify").FastifyRequest} request
|
||||
* @param {import("fastify").FastifyReply} reply
|
||||
* @param {import("fastify").FastifyInstance} fastify
|
||||
*
|
||||
* @returns {import('fastify').FastifyReply}
|
||||
*/
|
||||
export function totpVerify(request, reply, fastify) {
|
||||
try {
|
||||
const user = request.user;
|
||||
if (!authDB.checkUser(user)) {
|
||||
return reply.code(404).send({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const userTOTP = authDB.getUser(user);
|
||||
if (!userTOTP || !userTOTP.totpHash) {
|
||||
return reply.code(400).send({ error: '2FA not set up for this user' });
|
||||
}
|
||||
const isValid = verifyTOTP(userTOTP.totpHash, request.body.token);
|
||||
if (!isValid) {
|
||||
return reply.code(401).send({ error: 'Invalid 2FA token' });
|
||||
}
|
||||
|
||||
authDB.enableTOTP(user); // ensures it's flagged as active
|
||||
|
||||
return reply.code(200).send({ msg: '2FA verified successfully' });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ var env = process.env.NODE_ENV || 'development';
|
||||
|
||||
let database;
|
||||
|
||||
if (env === 'development') {
|
||||
if (!env || env === 'development') {
|
||||
database = new Database(":memory:", { verbose: console.log });
|
||||
} else {
|
||||
var dbPath = process.env.DB_PATH || '/db/db.sqlite'
|
||||
|
||||
Reference in New Issue
Block a user