Skip to content

Commit

Permalink
feat: add basic totp login to the api
Browse files Browse the repository at this point in the history
  • Loading branch information
cooperj committed Jan 30, 2025
1 parent 1216ede commit 09c6676
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 24 deletions.
56 changes: 39 additions & 17 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.3.2",
"drizzle-orm": "^0.31.0",
"express": "^4.18.2",
"generate-license-file": "^3.4.0",
"helmet": "^7.1.0",
Expand All @@ -37,8 +38,8 @@
"mqtt": "^5.7.0",
"mysql2": "^3.9.8",
"nodemailer": "^6.9.8",
"otpauth": "^9.3.6",
"showdown": "^2.1.0",
"drizzle-orm": "^0.31.0",
"ts-node": "^10.9.2"
},
"devDependencies": {
Expand Down
36 changes: 31 additions & 5 deletions api/src/controllers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { users as userSchema } from '@/db/schema';
import { eq, and, or, gt } from 'drizzle-orm';
import { hashPassword, comparePassword } from '@/utils/passwords';
import * as email from '@/communication/email';
import { generateSecureCode } from "@/utils/auth";
import { generateSecureCode, verifyTOTP } from "@/utils/auth";
import { log } from "@/utils/log";
import { verify } from "crypto";
const jwt = require('jsonwebtoken');
const dotenv = require('dotenv');
dotenv.config();

const login = async (req: Request, res: Response, next: NextFunction) => {
const { username, password } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress
const { username, password, totp } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress

// Search for user by username
const users = await db.select().from(userSchema)
Expand Down Expand Up @@ -42,6 +43,31 @@ const login = async (req: Request, res: Response, next: NextFunction) => {
});
}

if (users[0].totpEnabled) {

// Check if user even bothered to give us a totp code
if (!totp) {
await log(`Failed login from IP ${ip} - missing totp`)
return res.status(401).json({
message: "Please provide a TOTP code",
totpRequired: true
});
}

// Verify TOTP code
// const isTotpCorrect = verifyTOTP(users[0].totpSecret, totp);
const isTotpCorrect = verifyTOTP("3UWIINDEILTF67VQ3RVLXSWZZGX5REFF", totp);
console.log(isTotpCorrect, "is totp correct?")

if (isTotpCorrect === false) {
await log(`Failed login from IP ${ip} - invalid totp`)
return res.status(401).json({
message: "Invalid TOTP code",
totpRequired: true
});
}
}

// Create a JWT - make it last for 24 hours
const token = jwt.sign({ id: users[0].id, username: (users[0].username).toLowerCase() }, process.env.AUTH_SECRET, { expiresIn: '86400s' });

Expand All @@ -53,7 +79,7 @@ const login = async (req: Request, res: Response, next: NextFunction) => {

const forgotPassword = async (req: Request, res: Response, next: NextFunction) => {
const { username } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress

// Check for idiots
if (username.length === 0) {
Expand Down Expand Up @@ -105,7 +131,7 @@ const forgotPassword = async (req: Request, res: Response, next: NextFunction) =

const changePassword = async (req: Request, res: Response, next: NextFunction) => {
const { resetToken, password } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress
const serverTime = new Date();

// Search for user by username
Expand Down
20 changes: 19 additions & 1 deletion api/src/utils/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import e, { Request, Response, NextFunction } from "express";
import { Request, Response } from "express";
import { randomBytes } from 'crypto';
import * as OTPAuth from "otpauth";

export function generateSecureCode(): string {
const chars = '0123456789ABCDEF';
Expand Down Expand Up @@ -45,4 +46,21 @@ export function getTokenFromAuthCookie(req: Request, res: Response) {
}

return token
}

export function verifyTOTP (secret: string, token: string): boolean {
console.log(secret)
const _secret = OTPAuth.Secret.fromBase32(secret)
console.log(_secret)
const totp = new OTPAuth.TOTP({secret: _secret, digits: 6, period: 30, algorithm: 'SHA1'})
const realCode = totp.generate()
console.log(realCode, "real code")
console.log(token, "token")

const validationResponse = totp.validate({ token: token})
console.log(validationResponse, "validation response")
const validstep = validationResponse === null ? false : (typeof validationResponse === 'number' && validationResponse < 3 ? true : false);
console.log(validstep, "valid step")

return validstep
}
6 changes: 6 additions & 0 deletions jobs/src/uol-timetable/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,5 +283,11 @@ export function getEventType(rawEventType: string, rawName: string): IEventType
returnableEventType.name = ""
}

// Make all social events 'social'
if (rawName.toLowerCase().includes("social")) {
returnableEventType.type = "SOCIAL"
returnableEventType.name = ""
}

return returnableEventType
}

0 comments on commit 09c6676

Please sign in to comment.