diff --git a/src/node/util.ts b/src/node/util.ts index 551ed4b60..93d82851b 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -189,6 +189,66 @@ export function getPasswordMethod(hashedPassword: string | undefined): PasswordM return "SHA256" } +type PasswordValidation = { + isPasswordValid: boolean + hashedPassword: string +} + +type HandlePasswordValidationArgs = { + /** The PasswordMethod */ + passwordMethod: PasswordMethod + /** The password provided by the user */ + passwordFromRequestBody: string + /** The password set in PASSWORD or config */ + passwordFromArgs: string | undefined + /** The hashed-password set in HASHED_PASSWORD or config */ + hashedPasswordFromArgs: string | undefined +} + +/** + * Checks if a password is valid and also returns the hash + * using the PasswordMethod + */ +export async function handlePasswordValidation( + passwordValidationArgs: HandlePasswordValidationArgs, +): Promise { + const { passwordMethod, passwordFromArgs, passwordFromRequestBody, hashedPasswordFromArgs } = passwordValidationArgs + // TODO implement + const passwordValidation = { + isPasswordValid: false, + hashedPassword: "", + } + + switch (passwordMethod) { + case "PLAIN_TEXT": { + const isValid = passwordFromArgs ? safeCompare(passwordFromRequestBody, passwordFromArgs) : false + passwordValidation.isPasswordValid = isValid + + const hashedPassword = await hash(passwordFromRequestBody) + passwordValidation.hashedPassword = hashedPassword + break + } + case "SHA256": { + const isValid = isHashLegacyMatch(passwordFromRequestBody, hashedPasswordFromArgs || "") + passwordValidation.isPasswordValid = isValid + + passwordValidation.hashedPassword = hashedPasswordFromArgs || (await hashLegacy(passwordFromRequestBody)) + break + } + case "ARGON2": { + const isValid = await isHashMatch(passwordFromRequestBody, hashedPasswordFromArgs || "") + passwordValidation.isPasswordValid = isValid + + passwordValidation.hashedPassword = hashedPasswordFromArgs || "" + break + } + default: + break + } + + return passwordValidation +} + const mimeTypes: { [key: string]: string } = { ".aac": "audio/x-aac", ".avi": "video/x-msvideo", diff --git a/test/unit/node/util.test.ts b/test/unit/node/util.test.ts index 11fb701a1..5927ca326 100644 --- a/test/unit/node/util.test.ts +++ b/test/unit/node/util.test.ts @@ -1,6 +1,7 @@ import { hash, isHashMatch, + handlePasswordValidation, PasswordMethod, getPasswordMethod, hashLegacy, @@ -232,3 +233,92 @@ describe("getPasswordMethod", () => { expect(passwordMethod).toEqual(expected) }) }) + +describe.only("handlePasswordValidation", () => { + it("should return true with a hashedPassword for a PLAIN_TEXT password", async () => { + const p = "password" + const passwordValidation = await handlePasswordValidation({ + passwordMethod: "PLAIN_TEXT", + passwordFromRequestBody: p, + passwordFromArgs: p, + hashedPasswordFromArgs: undefined, + }) + + const matchesHash = await isHashMatch(p, passwordValidation.hashedPassword) + + expect(passwordValidation.isPasswordValid).toBe(true) + expect(matchesHash).toBe(true) + }) + it("should return false when PLAIN_TEXT password doesn't match args", async () => { + const p = "password" + const passwordValidation = await handlePasswordValidation({ + passwordMethod: "PLAIN_TEXT", + passwordFromRequestBody: "password1", + passwordFromArgs: p, + hashedPasswordFromArgs: undefined, + }) + + const matchesHash = await isHashMatch(p, passwordValidation.hashedPassword) + + expect(passwordValidation.isPasswordValid).toBe(false) + expect(matchesHash).toBe(false) + }) + it("should return true with a hashedPassword for a SHA256 password", async () => { + const p = "helloworld" + const passwordValidation = await handlePasswordValidation({ + passwordMethod: "SHA256", + passwordFromRequestBody: p, + passwordFromArgs: undefined, + hashedPasswordFromArgs: "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af", + }) + + const matchesHash = isHashLegacyMatch(p, passwordValidation.hashedPassword) + + expect(passwordValidation.isPasswordValid).toBe(true) + expect(matchesHash).toBe(true) + }) + it("should return false when SHA256 password doesn't match hash", async () => { + const p = "helloworld1" + const passwordValidation = await handlePasswordValidation({ + passwordMethod: "SHA256", + passwordFromRequestBody: p, + passwordFromArgs: undefined, + hashedPasswordFromArgs: "936a185caaa266bb9cbe981e9e05cb78cd732b0b3280eb944412bb6f8f8f07af", + }) + + const matchesHash = isHashLegacyMatch(p, passwordValidation.hashedPassword) + + expect(passwordValidation.isPasswordValid).toBe(false) + expect(matchesHash).toBe(false) + }) + it("should return true with a hashedPassword for a ARGON2 password", async () => { + const p = "password" + const passwordValidation = await handlePasswordValidation({ + passwordMethod: "ARGON2", + passwordFromRequestBody: p, + passwordFromArgs: undefined, + hashedPasswordFromArgs: + "$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY", + }) + + const matchesHash = await isHashMatch(p, passwordValidation.hashedPassword) + + expect(passwordValidation.isPasswordValid).toBe(true) + expect(matchesHash).toBe(true) + }) + it("should return false when ARGON2 password doesn't match hash", async () => { + const p = "password1" + const passwordValidation = await handlePasswordValidation({ + passwordMethod: "ARGON2", + passwordFromRequestBody: p, + passwordFromArgs: undefined, + hashedPasswordFromArgs: + "$argon2i$v=19$m=4096,t=3,p=1$0qR/o+0t00hsbJFQCKSfdQ$oFcM4rL6o+B7oxpuA4qlXubypbBPsf+8L531U7P9HYY", + }) + + const matchesHash = await isHashMatch(p, passwordValidation.hashedPassword) + + expect(passwordValidation.isPasswordValid).toBe(false) + expect(matchesHash).toBe(false) + }) +})