password hashing

This commit is contained in:
Felix Kaspar 2024-05-30 02:08:21 +02:00
parent fc3e2adc82
commit 187b7b3e78
6 changed files with 60 additions and 36 deletions

View File

@ -6,17 +6,21 @@ import { HeaderAPIKeyStrategy as HeaderAPIKeyStrategy } from "passport-headerapi
export function initialize(passport: typeof import("passport")) {
passport.use("local", new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, async function (err, user) {
User.findOne({ username: username }, function (err, user) {
if (err) {
return done(err, false);
}
if (!user) {
return done(null, false);
}
if (!await User.verifyPassword(user, password)) {
return done(null, false);
}
return done(null, user);
User.verifyPassword(user, password, (error, success) => {
if(error) return done(error, false);
if(!success) return done(null, false);
return done(null, user)
});
});
}
));

View File

@ -1,5 +1,6 @@
import { Error as SequelizeError, Op } from "sequelize";
import { Password, User } from "./user-model";
import { APIKey, Password, User } from "./user-model";
import crypto from "crypto";
type PickOne<T, F extends keyof T> = Pick<T, F> & { [K in keyof Omit<T, F>]?: never };
@ -28,28 +29,49 @@ export function findOne(params: {id?: number, username?: string, apikey?: string
);
}
export async function verifyPassword(user: User, password: string): Promise<boolean> {
const passwordRecord = await user.getPassword();
if(!passwordRecord) {
throw new Error("This user does not have a password set!");
}
return passwordRecord.password == password; // TODO: Replace with web-crypto
}
// TODO: Allow other authentication methods
export function createUser(params: { username: string, password: string }, cb: (err: SequelizeError | null, user: User | null) => void ) {
User.create({ username: params.username, authenticationMethod: "password" }).then(async user => {
user.setPassword(await Password.create({
password: params.password, // TODO: Replace with web-crypto
})).then(password => {
cb(null, user as any as User)
}).catch(e =>
cb(e, null)
);
User.create({ username: params.username }).then(async (user) => {
const salt = crypto.randomBytes(16).toString('hex');
hashPassword(params.password, salt, async (err, derivedKey) => {
if(err || !derivedKey) {
return cb(err, null);
}
user.setPassword(await Password.create({
password: derivedKey,
salt: salt
})).then(password => {
cb(null, user as any as User);
}).catch(e => {
cb(e, null);
});
})
}).catch(e =>
cb(e, null)
);
}
export function createAPIKey(user: User, apikey?: string) {
export async function verifyPassword(user: User, password: string, cb: (error: Error | null, success: boolean | null) => void) {
const passwordRecord = await user.getPassword();
if(!passwordRecord) {
return cb(new Error("This user does not have a password set!"), null);
}
hashPassword(password, passwordRecord.salt, (err, derivedKey) => {
if(err) return cb(err, null);
return cb(null, passwordRecord.password == derivedKey);
});
}
function hashPassword(password: string, salt: string, cb: (err: Error | null, derivedKey: string | null) => void) {
crypto.pbkdf2(password, salt, 100000, 64, 'sha512', (err, derivedKey) => {
if (err) return cb(err, null);
cb(null, derivedKey.toString('hex'));
});
}
export function createAPIKey(user: User, cb: (err: SequelizeError | null, apikey: APIKey | null) => void ) {
user.addAPIKey()
}

View File

@ -14,7 +14,6 @@ export class User extends Model<InferAttributes<User>, InferCreationAttributes<U
declare id: CreationOptional<number>;
declare username: string;
declare mail?: string;
declare authenticationMethod: string;
declare getPassword: HasOneGetAssociationMixin<Password | undefined>; // Note the null assertions!
declare setPassword: HasOneSetAssociationMixin<Password | undefined, number>;
@ -29,7 +28,7 @@ export class User extends Model<InferAttributes<User>, InferCreationAttributes<U
declare hasAccessRule: HasManyHasAssociationMixin<AccessRule | undefined, number>;
declare hasAccessRules: HasManyHasAssociationsMixin<AccessRule | undefined, number>;
declare countAccessRules: HasManyCountAssociationsMixin;
declare createAccessRule: HasManyCreateAssociationMixin<AccessRule, 'userId'>;
declare createAccessRule: HasManyCreateAssociationMixin<AccessRule, 'ownerId'>;
declare getAPIKeys: HasManyGetAssociationsMixin<APIKey | undefined>; // Note the null assertions!
declare addAPIKey: HasManyAddAssociationMixin<APIKey | undefined, number>;
@ -40,7 +39,7 @@ export class User extends Model<InferAttributes<User>, InferCreationAttributes<U
declare hasAPIKey: HasManyHasAssociationMixin<APIKey | undefined, number>;
declare hasAPIKeys: HasManyHasAssociationsMixin<APIKey | undefined, number>;
declare countAPIKeys: HasManyCountAssociationsMixin;
declare createAPIKey: HasManyCreateAssociationMixin<APIKey, 'userId'>;
declare createAPIKey: HasManyCreateAssociationMixin<APIKey, 'ownerId'>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
@ -49,6 +48,7 @@ export class User extends Model<InferAttributes<User>, InferCreationAttributes<U
export class Password extends Model<InferAttributes<Password>, InferCreationAttributes<Password>> {
declare id: CreationOptional<number>;
declare password: string;
declare salt: string;
declare ownerId: ForeignKey<User['id']>;
declare owner?: NonAttribute<User>;
@ -61,8 +61,8 @@ export class AccessRule extends Model<InferAttributes<AccessRule>, InferCreation
declare id: CreationOptional<number>;
declare grants: string;
declare userId: ForeignKey<User['id']>;
declare user?: NonAttribute<User>;
declare ownerId: ForeignKey<User['id']>;
declare owner?: NonAttribute<User>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
@ -72,8 +72,8 @@ export class APIKey extends Model<InferAttributes<APIKey>, InferCreationAttribut
declare id: CreationOptional<number>;
declare apikey: string;
declare userId: ForeignKey<User['id']>;
declare user?: NonAttribute<User>;
declare ownerId: ForeignKey<User['id']>;
declare owner?: NonAttribute<User>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;

View File

@ -23,10 +23,6 @@ User.init(
type: DataTypes.STRING,
unique: true,
},
authenticationMethod: {
type: DataTypes.STRING,
allowNull: false,
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},
@ -41,6 +37,7 @@ Password.init(
primaryKey: true
},
password: DataTypes.STRING,
salt: DataTypes.STRING,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},

View File

@ -49,10 +49,11 @@ import api from "./routes/api/api-controller";
* auth
*/
console.log(import.meta.env)
console.log("env", import.meta.env)
if(import.meta.env.VITE_AUTH_ENABLED === "True") {
import("./auth/auth-controller.ts").then(router => router.connect(app)).finally(() => {
console.log("Attatching Auth")
import("./auth/auth-controller").then(router => router.connect(app)).finally(() => {
/*
* api
*/