From ffa4496c166d5e09e4eb35b98f20b0d50d37d556 Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Tue, 18 Jul 2023 09:14:33 +0200 Subject: [PATCH] add initial user Management API --- src/models/User.ts | 12 ++++++ src/routes/api/login/+server.ts | 54 +++++++++++++++++++++++++ src/routes/api/register/+server.ts | 64 ++++++++++++++++++++++++++++++ src/routes/api/verify/+server.ts | 63 +++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 src/models/User.ts create mode 100644 src/routes/api/login/+server.ts create mode 100644 src/routes/api/register/+server.ts create mode 100644 src/routes/api/verify/+server.ts diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..6d3ab25 --- /dev/null +++ b/src/models/User.ts @@ -0,0 +1,12 @@ +import mongoose from 'mongoose'; + +const UserSchema = new mongoose.Schema( + { + username: {type: String, required: true, unique: true}, + pass_hash: {type: String, required: true}, + salt : {type: String, required: true}, + access: [String], //rezepte, flims, abrechnung, ... + }, {timestamps: true} +); + +export const User = mongoose.model("User", UserSchema); diff --git a/src/routes/api/login/+server.ts b/src/routes/api/login/+server.ts new file mode 100644 index 0000000..dc97db7 --- /dev/null +++ b/src/routes/api/login/+server.ts @@ -0,0 +1,54 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit'; +import { sign } from 'jsonwebtoken'; +import { hash, verify} from 'argon2'; +import { COOKIE_SECRET } from '$env/static/private' + +import { dbConnect, dbDisconnect } from '../../../utils/db'; +import { User } from '../../../models/User'; + +// header: use for bearer token for now +// recipe json in body +export const POST: RequestHandler = async ({request}) => { + const {username, password} = await request.json() + // TODO: get salt from user in DB + await dbConnect() + let res = await User.findOne({username: username}, 'pass_hash salt').lean() + await dbDisconnect() + if(!res){ + throw error(401, {message: "wrong password or user does not exist"}) + } + + const stored_pw = res.pass_hash + const salt = res.salt + + const isMatch = await verify(stored_pw, password, {salt}) + if(!isMatch){ + throw error(401, {message: "wrong password or user does not exist"}) + } + + res = await createJWT(username) + return new Response(JSON.stringify(res)) +}; + +async function hashPassword(password) { + try { + const salt = await generateSalt(); // Generate a random salt + const hashedPassword = await hash(password, salt); // Hash the password with the salt + return { hashedPassword, salt }; + } catch (error) { + console.error('Error hashing password:', error); + } +} + + +async function createJWT(username) { + const payload = { + username: username, + }; + + const masterSecret = COOKIE_SECRET; + const secretKey = masterSecret; + const jwt = sign(payload, secretKey); + return jwt +} diff --git a/src/routes/api/register/+server.ts b/src/routes/api/register/+server.ts new file mode 100644 index 0000000..61f8cb3 --- /dev/null +++ b/src/routes/api/register/+server.ts @@ -0,0 +1,64 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit'; +import { sign } from 'jsonwebtoken'; +import { hash, verify } from 'argon2'; +import { randomBytes } from 'crypto'; +import { COOKIE_SECRET } from '$env/static/private' +import { ALLOW_REGISTRATION } from '$env/static/private' + +import { User } from '../../../models/User'; +import { dbConnect, dbDisconnect } from '../../../utils/db'; + +// header: use for bearer token for now +// recipe json in body +export const POST: RequestHandler = async ({request}) => { + if(ALLOW_REGISTRATION){ + const {username, password, access} = await request.json() + const salt = randomBytes(32).toString('hex'); // Generate a random salt + + const pass_hash = await hashPassword(password, salt) + await dbConnect(); + try{ + await User.create({ + username: username, + pass_hash: pass_hash, + salt: salt, + access: access, + }) + }catch(e){ + await dbDisconnect(); + throw error(400, e); + } + await dbDisconnect(); + return new Response(JSON.stringify({message: "User added successfully"}), + {status: 200} + ); + } + else{ + throw error(401, "user registration currently closed") + } + + +}; + +async function hashPassword(password, salt) { + try { + const hashedPassword = await hash(password, salt); // Hash the password with the salt + return hashedPassword; + } catch (error) { + console.error('Error hashing password:', error); + } +} + + + +async function createJWT(username, userSalt) { + const payload = { + username: username, + }; + + const masterSecret = COOKIE_SECRET; + const secretKey = masterSecret + userSalt; + const jwt = sign(payload, secretKey); + return jwt +} diff --git a/src/routes/api/verify/+server.ts b/src/routes/api/verify/+server.ts new file mode 100644 index 0000000..927b138 --- /dev/null +++ b/src/routes/api/verify/+server.ts @@ -0,0 +1,63 @@ +import type { RequestHandler } from '@sveltejs/kit'; +import { error } from '@sveltejs/kit'; +import { verify } from 'jsonwebtoken'; +import { hash} from 'argon2'; +import { randomBytes } from 'crypto'; +import { COOKIE_SECRET } from '$env/static/private' +import { ALLOW_REGISTRATION } from '$env/static/private' + +import { User } from '../../../models/User'; +import { dbConnect, dbDisconnect } from '../../../utils/db'; + + +import { getJWTFromRequest } from '../../../utils/cookie'; +// header: use for bearer token for now +// recipe json in body +export const GET: RequestHandler = async ({request}) => { + const jwt = getJWTFromRequest(request) + console.log(jwt) + + // Set your master secret key (replace with your own secret) + const masterSecret = COOKIE_SECRET; + const secretKey = masterSecret + let decoded + try{ + decoded = await verify(jwt, secretKey); + } + catch(e){ + throw error(401, "Cookies have changed, please log in again") + } + + await dbConnect() + let res = await User.findOne({username: decoded.username}, 'access').lean(); + await dbDisconnect() + if(!res){ + throw error(404, "User for this Cookie does no longer exist") + } + return new Response(JSON.stringify({ + username: decoded.username, + access: res.access + }), {status: 200}) +}; + +async function hashPassword(password, salt) { + try { + const hashedPassword = await hash(password, salt); // Hash the password with the salt + return hashedPassword; + } catch (error) { + console.error('Error hashing password:', error); + } +} + + + +async function createJWT(username, userSalt) { + const payload = { + username: username, + }; + + const masterSecret = COOKIE_SECRET; + const secretKey = masterSecret + userSalt; + const jwt = sign(payload, secretKey); + return jwt +}