Attempted adding of 'Salting' for the password, a method which adds random numbers or letters to make rainbowtable password cracking impossible, not tested yet

This commit is contained in:
Esad Mustafoski
2025-02-02 12:33:47 +01:00
parent 3c6fdd0b59
commit 4c80caa52a
4 changed files with 399 additions and 311 deletions

View File

@@ -1,18 +1,16 @@
/// <reference lib="deno.ns" />
/**
/**
* @author Esad Mustafoski
* @file api/helpers.ts
* @description Helper functions for the API
*
*/
import { Context } from "https://deno.land/x/oak/mod.ts";
import { encodeHex } from "jsr:@std/encoding/hex";
// import { hash } from "node:crypto";
export type ApiResponse = {
status: number;
body: unknown;
status: number;
body: unknown;
};
// --- Helper Functions --- //
@@ -22,9 +20,9 @@ export type ApiResponse = {
* Status is the HTTP Status code
* Body is the response body/message/data.
*/
const sendResponse = (ctx: Context, {status, body}: ApiResponse): void => {
ctx.response.status = status;
ctx.response.body = body as any;
const sendResponse = (ctx: Context, { status, body }: ApiResponse): void => {
ctx.response.status = status;
ctx.response.body = body as any;
};
/**
@@ -32,24 +30,41 @@ const sendResponse = (ctx: Context, {status, body}: ApiResponse): void => {
* @see sendResponse
*/
const errorResponse = (ctx: Context, status: number, message: string): void => {
sendResponse(ctx, { status, body: { error: message } });
sendResponse(ctx, { status, body: { error: message } });
};
/**
* @description Hashing Function for Passwords/etc
* @param password The password to hash
/**
* @description password "Salter", used to salt the passwords before the hash, this salt will be
* returned seperately to save the salt in the DB
* @param password The password to salt
* @returns {saltedPassword: string, salt: string} Password with the salt + Salt seperately, both strings
*/
const hashPassword = async(password: string): Promise<string> => {
const to_hash = password;
const buffer = new TextEncoder().encode(to_hash);
const hash_buffer = await crypto.subtle.digest("SHA-256", buffer);
const hash = await encodeHex(hash_buffer);
return hash;
}
const saltPassword = async (
password: string,
): Promise<{ saltedPassword: string; salt: string }> => {
const saltBytes = new Uint8Array(16); // 16 bytes = 128 bits for randomness
await crypto.getRandomValues(saltBytes);
const salt = encodeHex(saltBytes);
export {
sendResponse,
errorResponse,
hashPassword
const saltedPassword = `${password}${salt}`;
return {
saltedPassword,
salt,
};
};
/**
* @description Hashing Function for Passwords/etc
* @param password The password to hash
* @returns {hash: string} The hashed password as a string
*/
const hashPassword = async (password: string): Promise<string> => {
const to_hash = password;
const buffer = new TextEncoder().encode(to_hash);
const hash_buffer = await crypto.subtle.digest("SHA-256", buffer);
const hash = await encodeHex(hash_buffer);
return hash;
};
export { errorResponse, hashPassword, saltPassword, sendResponse };

View File

@@ -1,11 +1,16 @@
/// <reference lib="deno.ns" />
/**
* @author Esad Mustafoski
* Main API file, Handles all the routing/api stuff
* @ðescription Main API file, Handles all the routing/api stuff
*/
// +++ IMPORTS ------------------------------------------------------ //
import { Application, Router, Context, Next } from "https://deno.land/x/oak/mod.ts";
import {
Application,
Context,
Next,
Router,
} from "https://deno.land/x/oak/mod.ts";
import { oakCors } from "https://deno.land/x/cors/mod.ts";
import * as db_utils from "../database/utils.ts";
import * as helper_utils from "./helpers.ts";
@@ -14,11 +19,11 @@ import * as helper_utils from "./helpers.ts";
const router = new Router();
const app = new Application();
// For the future
// unused for now
type ApiResponse = {
status: number;
body: unknown;
}
status: number;
body: unknown;
};
// +++ ROUTER ------------------------------------------------------- //
// Creates the routes for the API server.
@@ -27,13 +32,17 @@ type ApiResponse = {
// Docs Routes
router
.get("/", (ctx) => { ctx.response.body = "For endpoints, use /api/{name}"; })
.get("/api", (ctx) => { ctx.response.body = "For API Documentation, visit /docs"; })
.get("/", (ctx) => {
ctx.response.body = "For endpoints, use /api/{name}";
})
.get("/api", (ctx) => {
ctx.response.body = "For API Documentation, visit /docs";
});
// Account routes
// -- Account routes --
router
.post("/api/account/login", api_login) // TODO
.post("/api/account/register", api_register) // TODO
.post("/api/account/login", api_login)
.post("/api/account/register", api_register)
.post("/api/account/logout", () => {}) // TODO
.post("/api/account/password/forgot", () => {}) // TODO
.post("/api/account/password/reset", () => {}) // TODO
@@ -41,177 +50,196 @@ router
.post("/api/account/email/change-email", () => {}) // TODO
.post("/api/account/email/verify-email", () => {}) // TODO
.post("/api/account/delete-account", () => {}) // TODO
.post("/api/account/block", () => {}) // TODO
.post("/api/account/block", () => {}); // TODO
// Auth Routes
// -- Auth Routes --
router
.get("/api/auth", () => {}) // TODO
.get("/api/auth/verify", () => {}) // TODO
.get("/api/auth/refresh", () => {}) // TODO
.get("/api/auth/refresh", () => {}); // TODO
// User routes
// -- User routes --
router
.get("/api/users", api_getAllUsers)
.get("/api/user/:id/info", api_user_getInfo); // @error GEHT NICHT
.get("/api/user/:id/info", api_user_getInfo);
// Post routes
// -- Post routes --
router
.get("/api/posts", api_posts_getAll);
// +++ FUNCTIONS ----------------------------------------------------- //
/**
* @description Stands between the client and the API
* It checks if the client is authorized to access the API with a token/Multiple tokens
* Currently not implemented
* Middleware
*/
async function authenticator(ctx: Context, next: Next): Promise<void> {
const authHeader = ctx.request.headers.get('Authorization');
const authHeader = ctx.request.headers.get("Authorization");
if (!authHeader) {
ctx.response.status = 401;
ctx.response.body = { error: "No header" };
return;
}
const match = authHeader.match(/^Bearer (.+)$/);
if (!match) {
ctx.response.status = 401;
ctx.response.body = { error: "Invalid format" };
return;
}
if (!authHeader) {
ctx.response.status = 401;
ctx.response.body = { error: "No header" };
return;
}
const token = match[1];
const match = authHeader.match(/^Bearer (.+)$/);
if (!match) {
ctx.response.status = 401;
ctx.response.body = { error: "Invalid format" };
return;
}
try {
// Token logic missing, not tried or attempted yet.
await next();
} catch (error) {
ctx.response.status = 401;
ctx.response.body = { error: "Invalid token" };
}
const token = match[1];
try {
// Token logic missing, not tried or attempted yet.
await next();
} catch (error) {
ctx.response.status = 401;
ctx.response.body = { error: "Invalid token" };
}
}
async function tokenChecker(ctx: Context, next: Next): Promise<void> {
// logic below (TODO):
/**
* 1. check if the token is valid
* 2. if valid, set the user in the context
* 3. if not valid, return 401
* 4. if token is missing, return 401
* 5. if token is expired, return 401
* 6. if token is blacklisted, return 401
* 7. if token is not in the database, return 401
* 8. if token is not associated with a user, return 401
* 9. if token is not associated with the correct user, return 401
*/
async function tokenChecker(ctx: Context, next: Next): Promise<void> {
// logic below (TODO):
/**
* 1. check if the token is valid
* 2. if valid, set the user in the context
* 3. if not valid, return 401
* 4. if: token missing, expired, blacklisted, !DB, !user or !correct user, return 401 with associated error
* eg: wrong user: 401 -> "Token not associated with this user"
*/
}
async function api_getAllUsers(ctx: Context): Promise<void> {
const getUsers = await db_utils.getAllUsersFromDB();
ctx.response.body = getUsers;
const getUsers = await db_utils.getAllUsersFromDB();
ctx.response.body = getUsers;
}
// Users
async function api_user_getInfo(ctx: any): Promise<void> {
const id = ctx.params.id;
if (!id) {
ctx.response.status = 400; // Bad Request status
ctx.response.body = { error: "User ID required" };
return;
const id = ctx.params.id;
if (!id) {
ctx.response.status = 400;
ctx.response.body = { error: "User ID required" };
return;
}
try {
const user = await db_utils.getAllUserInfoByID(id);
if (user === null || user === undefined) {
helper_utils.errorResponse(ctx, 404, "User not found");
return;
}
try {
const user = await db_utils.getAllUserInfoByID(id);
if (user === null || user === undefined) {
helper_utils.errorResponse(ctx, 404, "User not found");
return;
}
ctx.response.body = user;
} catch (error) {
helper_utils.errorResponse(ctx, 500, "Internal Server Error");
}
ctx.response.body = user;
} catch (error) {
helper_utils.errorResponse(ctx, 500, error as string);
}
}
// Posts
async function api_posts_getAll(ctx: Context): Promise<void> {
const posts = await db_utils.getPostsFromDB();
ctx.response.body = posts;
const posts = await db_utils.getPostsFromDB();
ctx.response.body = posts;
}
// Comments
// Comments (missing)
// login/register
async function api_register(ctx: Context): Promise<void> {
try {
const body = ctx.request.body;
const result = await body.json();
const { username, password, userGroup, displayname, user_email, firstname, surname} = result;
const account_created = `${Math.floor(Date.now() / 1000)}-${new Date().toLocaleDateString('en-GB').split('/').join('-')}`;
try {
const body = ctx.request.body;
const result = await body.json();
const {
username,
password,
userGroup,
displayname,
user_email,
firstname,
surname,
} = result;
const account_created = `${Math.floor(Date.now() / 1000)}-${
new Date().toLocaleDateString("en-GB").split("/").join("-")
}`;
if ( !username || !password || !userGroup || !displayname || !user_email || !firstname || !surname) {
helper_utils.errorResponse(ctx, 400, "Missing required fields");
return;
}
const hash = await helper_utils.hashPassword(password);
const userId = await db_utils.registerUser(username, hash, userGroup, displayname, user_email, firstname, surname, account_created);
helper_utils.sendResponse(ctx, { status: 200, body: `Registered under name: ${userId}` });
} catch (error) {
console.log(error);
helper_utils.errorResponse(ctx, 500, "Invalid request");
return;
if (
!username || !password || !userGroup || !displayname || !user_email ||
!firstname || !surname
) {
helper_utils.errorResponse(ctx, 400, "Missing required fields");
return;
}
// First salt the password
const { saltedPassword, salt } = await helper_utils.saltPassword(password);
// Then hash the salted password
const hash = await helper_utils.hashPassword(saltedPassword);
const userId = await db_utils.registerUser(
username,
hash,
salt,
userGroup,
displayname,
user_email,
firstname,
surname,
account_created,
);
helper_utils.sendResponse(ctx, {
status: 200,
body: `Registered under name: ${userId}`,
});
} catch (error) {
console.log(error);
helper_utils.errorResponse(ctx, 500, "Invalid request");
return;
}
}
async function api_login(ctx: Context): Promise<string> {
try {
const body = ctx.request.body;
const result = await body.json();
const { username, password } = result;
try {
const body = ctx.request.body;
const result = await body.json();
const { username, password } = result;
if (!username || !password) {
helper_utils.errorResponse(ctx, 400, "Missing required fields");
return "Error";
}
const user = await db_utils.getUserByUsername(username);
if (!user) {
helper_utils.errorResponse(ctx, 404, "User not found");
return "Error";
}
const hash = await helper_utils.hashPassword(password);
if (user.password !== hash) {
helper_utils.errorResponse(ctx, 401, "Invalid password");
return "Error";
}
} catch (error) {
console.log(error);
helper_utils.errorResponse(ctx, 500, "Invalid request");
return "Error";
if (!username || !password) {
helper_utils.errorResponse(ctx, 400, "Missing required fields");
return "Error";
}
const user = await db_utils.getUserByUsername(username);
if (!user) {
helper_utils.errorResponse(ctx, 404, "User not found");
return "Error";
}
// Get the stored salt for this user
const storedSalt = user.password_salt;
// Salt the provided password with the stored salt
const saltedPassword = `${password}${storedSalt}`;
// Hash the salted password
const hash = await helper_utils.hashPassword(saltedPassword);
// Compare with stored hash
if (user.password !== hash) {
helper_utils.errorResponse(ctx, 401, "Invalid password");
return "Error";
}
helper_utils.sendResponse(ctx, { status: 200, body: "Success" });
return "Success";
} catch (error) {
console.log(error);
helper_utils.errorResponse(ctx, 500, "Invalid request");
return "Error";
}
}
// Filtering
// +++ APP ---------------------------------------------------------- //
app.use(oakCors({
origin: '*',
credentials: true,
origin: "*",
credentials: true,
}));
app.use(router.routes());
app.use(router.allowedMethods());