diff --git a/backend/src/User/Controller.ts b/backend/src/User/Controller.ts index 081c107..193fdf1 100644 --- a/backend/src/User/Controller.ts +++ b/backend/src/User/Controller.ts @@ -6,21 +6,39 @@ import { authenticator } from 'otplib'; const user = new User(); const userController = new Elysia() + .get('/', ({user}) => { + return user + }) .post('/login', async ({ body }) => { - const msg = db.query(`select id, password, otp from users WHERE username = ?;`) + const msg = db.query(`select id, password, administrator, otp from users WHERE username = ?;`) .get(body.username) console.log(msg) if(msg == null) return new Response("Invalid username or password", { status: 401 }) const validPassword = await Bun.password.verify(body.password, msg.password) if (!validPassword) return new Response("Invalid username or password", { status: 401 }) // if(!authenticator.check(body.otp, msg.otp)) return new Response("Invalid OTP code", { status: 401 }) + if(msg.administrator == 1) {return new Response(await user.createToken(msg.id), {status: 418})} return await user.createToken(msg.id) }) - .post('/register', async({body}) => { - return await Bun.password.hash(body.password); - }) + // .post('/register', async({body}) => { + // return await Bun.password.hash(body.password); + // }) .get('/otp', () => { return authenticator.generateSecret(); }) + .put('/password', ({user}, body) => { + const oldPassword = body.oldPassword; + const newPassword = body.newPassword; + if (oldPassword == "" || newPassword == "") { + {return new Response("Bad password", {status: 400})} + } + + const msg = db.query(`select password from users WHERE id = ?;`).get(user.id); + if(!Bun.password.verify(oldPassword, msg.password)) { + {return new Response("Bad old password", {status: 400})} + } + const newPasswordHash = Bun.password.hash(body.newPassword) + const msg = db.query(`update users set password = ? WHERE id = ?;`).run(newPassword. user.id); + }) export default userController diff --git a/backend/src/admin/Controller.ts b/backend/src/admin/Controller.ts index 732fc7c..af2d949 100644 --- a/backend/src/admin/Controller.ts +++ b/backend/src/admin/Controller.ts @@ -1,10 +1,48 @@ import { Elysia } from 'elysia' +import db from '../Database' +import { authenticator } from 'otplib'; const adminController = new Elysia() .get('/', () => "admin endpoint") -.post('/register', async({body}) => { - return await Bun.password.hash(body.password); +.get('/users', () => { + const users = db.query(`SELECT id, username, name FROM users;`) + .all(); + return users +}) +.post('/user/group/:groupId', ({params: {groupId}, body}) => { + db.query(`INSERT INTO userGroups (UserID, GroupID) VALUES (?, ?);`).run(groupId, body.userId); + return "Added user to group" +}) +.delete('/user/:userId', ({params: {userId}}) => { + db.query(`DELETE FROM users WHERE id = ?;`).run(userId); + "deleted user" +}) +.post('/register', async({body}) => { + const password = await Bun.password.hash(body.password); + const otp = authenticator.generateSecret(); + const res = db.query(`INSERT INTO users(username, password, name, otp) VALUES (?, ?, ?, ?);`).run(body.username, password, body.name, otp); + return {id: res.lastInsertRowid, username: body.username, name: body.name, otp: otp} +}) +.get('/group', () => { + const groups = db.query(`SELECT GroupID, GroupName FROM groups;`) + .all(); + return groups +}) +.post('/group', ({body}) => { + const res = db.query(`INSERT INTO groups (GroupName) VALUES (?);`).run(body.name); + return {GroupID: res.lastInsertRowid, GroupName: body.name} +}) +.delete('/group/:groupId', ({ params: { groupId }}) => { + db.query(`DELETE FROM groups WHERE GroupID = ?;`).run(groupId); + + "deleted group" +}) +.delete('/password/:passwordId', ({params: {passwordId}}) => { + db.query(`DELETE FROM passwords WHERE id = ?;`).run(passwordId); + + "deleted password" }) + export default adminController \ No newline at end of file diff --git a/backend/src/bun.lockb b/backend/src/bun.lockb index c1a978e..d271ee9 100755 Binary files a/backend/src/bun.lockb and b/backend/src/bun.lockb differ diff --git a/backend/src/index.ts b/backend/src/index.ts index 9a47ea3..f209e08 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,9 +1,41 @@ import {Elysia} from 'elysia' import userController from './User/Controller' import adminController from './admin/Controller' +import passwordController from './password/Controller' +import type { User } from './interfaces/user_interface' +import db from './Database' +import { cors } from '@elysiajs/cors' const app = new Elysia() -.get('/', () => "Hello") +.derive(async ({ headers }) => { + const auth = headers['authorization'] + if (!auth) { + return {} + } + const bearer = auth?.startsWith('Bearer ') ? auth.slice(7) : null + + + const msg = db.query(`select users.id, users.name, users.administrator from users JOIN tokens ON users.id = tokens.user_id WHERE tokens.token = ?;`) + .get(bearer) + console.log(msg) + if(msg == null) { + throw new Response("Invalid token", { status: 401 }) + return {} + } + + const user: User = { + id: msg.id, + username: msg.username, + name: msg.name, + admin: msg.administrator + } + return { user } + + // return user here instead of bearer +}) +.get('/', ({ user }) => user) .group('/user', (app) => app.use(userController)) .group('/admin', (app) => app.use(adminController)) +.group('/password', (app) => app.use(passwordController)) +.use(cors()) .listen(3000) \ No newline at end of file diff --git a/backend/src/interfaces/user_interface.ts b/backend/src/interfaces/user_interface.ts new file mode 100644 index 0000000..b77ccb5 --- /dev/null +++ b/backend/src/interfaces/user_interface.ts @@ -0,0 +1,7 @@ +export type User = { + id: number, + name: string, + username: string, + password?: string, + admin: boolean, +} \ No newline at end of file diff --git a/backend/src/package.json b/backend/src/package.json index fee179a..8c1c74a 100644 --- a/backend/src/package.json +++ b/backend/src/package.json @@ -9,6 +9,7 @@ "typescript": "^5.0.0" }, "dependencies": { + "@elysiajs/cors": "^1.1.1", "elysia": "^1.1.12", "kysely": "^0.27.4", "nanoid": "^5.0.7", diff --git a/backend/src/password/Controller.ts b/backend/src/password/Controller.ts new file mode 100644 index 0000000..127f10c --- /dev/null +++ b/backend/src/password/Controller.ts @@ -0,0 +1,44 @@ +import { Elysia } from 'elysia' +import db from '../Database' + + +const passwordController = new Elysia() +.get('/', ({ user }) => { + const userGroup = db.query(`SELECT groups.GroupId, groups.GroupName FROM groups JOIN userGroups ON groups.GroupID = userGroups.GroupID JOIN users ON userGroups.UserID = users.id WHERE users.id = ?;`) + .all(user.id); + return userGroup +}) +.get('/:groupId', ({params: {groupId}}) => { + // make sure user has access to group + const passwords = db.query(`SELECT id, name, password, created_by, created_at from passwords WHERE group_id = ?;`) + .all(groupId); + console.log(passwords) + return passwords +}) +.post('/:groupId', async({params: {groupId}, body, user}) => { + const passName = body.name; + const password = body.password + + if (!passName || passName.trim() === "") { + return new Response("Name must be defined", { status: 400 }); + } + + const userGroup = db.query(`SELECT 1 FROM userGroups WHERE userID = ? AND groupID = ?;`) + .get(user.id, groupId); + + if (!userGroup) { + return new Response("Forbidden: You do not have access to this group", { status: 403 }); + } + + db.query(`INSERT INTO passwords (name, password, group_id, created_by) VALUES (?, ?, ?, ?);`).run(body.name, body.password, groupId, user.name); + + + return new Response("Password created successfully", { status: 201 }); +}) +.delete('/:passwordId', ({params: {passwordId}}) => { + db.query(`DELETE FROM users WHERE id = ?;`).run(userId); + + "deleted password" +}) + +export default passwordController \ No newline at end of file diff --git a/backend/src/utils/groups.ts b/backend/src/utils/groups.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/utils/send_mail.ts b/backend/src/utils/send_mail.ts new file mode 100644 index 0000000..af3da9f --- /dev/null +++ b/backend/src/utils/send_mail.ts @@ -0,0 +1,51 @@ +import { SmtpClient } from 'smtp' +import config from "../email_config.json" assert { type: "json" }; + +const client = new SmtpClient(); + +export async function SendVerificationMail(userEmail: string, name: string, companyName: string, verificationToken: string) { + try { + await client.connect({ + hostname: config.smtp_server, + port: config.smtp_port, + username: config.smtp_username, + password: config.smtp_password, + }); + + await client.send({ + from: "PasswordBox ", + to: userEmail, + subject: "=?utf-8?B?QmVrcsOmZnQgZGluIGUtbWFpbC1hZHJlc3Nl?=", + content: `Kære ${name},

Velkommen til vores interne password manager PasswordBox. For at fuldføre oprettelsesprocessen skal du bekræfte din e-mail-adresse ved at klikke på nedenstående link:

https://passwordbox.dk/verify?token=${verificationToken}

Hvis du ikke vil tilmeldes vores tjeneste, kan du blot ignorere denne e-mail.

Med venlig hilsen,
PasswordBox

Denne e-mail kan ikke besvares`, + }); + } catch { + return error("Kunne ikke sende mail!"); + } finally { + await client.close(); + } + return ok(undefined) +} + +export async function SendForgotPasswordMail(userEmail: string, name: string, verificationToken: string) { + try { + await client.connect({ + hostname: config.smtp_server, + port: config.smtp_port, + username: config.smtp_username, + password: config.smtp_password, + }); + + await client.send({ + from: "PasswordBox ", + to: userEmail, + subject: "PasswordBox - Glemt adgangskode", + content: `Hej ${name},

Der er blevet anmodet et password reset.
Klik på følgende link for at nulstille din adgangskode:

https://passwordbox.dk/verify?token=${verificationToken}

Hvis du ikke har glemt din adgangskode, kan du blot ignorere denne e-mail.

Med venlig hilsen,
PasswordBox

Denne e-mail kan ikke besvares`, + }); + } catch { + return error("Kunne ikke sende mail!"); + } finally { + await client.close(); + } + return ok(undefined) +} +