-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0a86d20
commit 9dc36c8
Showing
10 changed files
with
145 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
"wrangler": "^3.0.0" | ||
}, | ||
"dependencies": { | ||
"@tsndr/cloudflare-worker-jwt": "^2.5.3", | ||
"hono": "^4.3.9" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Context } from 'hono'; | ||
import { HTTPException } from 'hono/http-exception'; | ||
import jwt from '@tsndr/cloudflare-worker-jwt'; | ||
|
||
async function createJWT(email: string) { | ||
const token = await jwt.sign({ email }, 'secret'); | ||
return token; | ||
} | ||
|
||
export default async function create(c: Context) { | ||
let body; | ||
let email: string | undefined, otp: string; | ||
const emailPattern = /^[a-zA-Z]+\d{2}[a-zA-Z]{3}\d{1,3}@iiitkottayam\.ac\.in$/; | ||
const otpPattern = /^\d{6}$/; | ||
try { | ||
body = await c.req.json(); | ||
email = body.email; | ||
otp = body.otp; | ||
} catch (e) { | ||
throw new HTTPException(400); | ||
} | ||
if (!otp || !otpPattern.test(otp) || !email || !emailPattern.test(email)) { | ||
throw new HTTPException(401); | ||
} | ||
|
||
return c.json({ | ||
status: 'success', | ||
data: { | ||
token: await createJWT(email), | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Context } from 'hono'; | ||
import { HTTPException } from 'hono/http-exception'; | ||
import jwt from '@tsndr/cloudflare-worker-jwt'; | ||
|
||
async function createJWT(email: string) { | ||
const token = await jwt.sign({ email }, 'secret'); | ||
return token; | ||
} | ||
|
||
export default async function verify(c: Context) { | ||
let body; | ||
let email: string | undefined, otp: string; | ||
const emailPattern = /^[a-zA-Z]+\d{2}[a-zA-Z]{3}\d{1,3}@iiitkottayam\.ac\.in$/; | ||
const otpPattern = /^\d{6}$/; | ||
try { | ||
body = await c.req.json(); | ||
email = body.email; | ||
otp = body.otp; | ||
} catch (e) { | ||
throw new HTTPException(400); | ||
} | ||
if (!otp || !otpPattern.test(otp) || !email || !emailPattern.test(email)) { | ||
throw new HTTPException(401); | ||
} | ||
|
||
return c.json({ | ||
status: 'success', | ||
data: { | ||
token: await createJWT(email), | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,15 @@ | ||
import { Hono } from 'hono'; | ||
import register from './handlers/register'; | ||
import verify from './handlers/verify'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions | ||
export type Env = { | ||
DATABASE_URL: string; | ||
}; | ||
|
||
const app = new Hono<{ Bindings: Env }>(); | ||
|
||
app.post('/auth/register', register); | ||
app.post('/auth/verify', verify); | ||
|
||
export default app; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export async function hashPassword(password: string, providedSalt?: Uint8Array): Promise<string> { | ||
const encoder = new TextEncoder(); | ||
// Use provided salt if available, otherwise generate a new one | ||
const salt = providedSalt || crypto.getRandomValues(new Uint8Array(16)); | ||
const keyMaterial = await crypto.subtle.importKey('raw', encoder.encode(password), { name: 'PBKDF2' }, false, [ | ||
'deriveBits', | ||
'deriveKey', | ||
]); | ||
const key = await crypto.subtle.deriveKey( | ||
{ | ||
name: 'PBKDF2', | ||
salt: salt, | ||
iterations: 100000, | ||
hash: 'SHA-256', | ||
}, | ||
keyMaterial, | ||
{ name: 'AES-GCM', length: 256 }, | ||
true, | ||
['encrypt', 'decrypt'], | ||
); | ||
const exportedKey = (await crypto.subtle.exportKey('raw', key)) as ArrayBuffer; | ||
const hashBuffer = new Uint8Array(exportedKey); | ||
const hashArray = Array.from(hashBuffer); | ||
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); | ||
const saltHex = Array.from(salt) | ||
.map((b) => b.toString(16).padStart(2, '0')) | ||
.join(''); | ||
return `${saltHex}:${hashHex}`; | ||
} | ||
|
||
export async function verifyPassword(storedHash: string, passwordAttempt: string): Promise<boolean> { | ||
const [saltHex, originalHash] = storedHash.split(':'); | ||
const matchResult = saltHex.match(/.{1,2}/g); | ||
if (!matchResult) { | ||
throw new Error('Invalid salt format'); | ||
} | ||
const salt = new Uint8Array(matchResult.map((byte) => parseInt(byte, 16))); | ||
const attemptHashWithSalt = await hashPassword(passwordAttempt, salt); | ||
const [, attemptHash] = attemptHashWithSalt.split(':'); | ||
return attemptHash === originalHash; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,19 +7,27 @@ import worker from '../src/index'; | |
// `Request` to pass to `worker.fetch()`. | ||
const IncomingRequest = Request<unknown, IncomingRequestCfProperties>; | ||
|
||
describe('Hello World worker', () => { | ||
it('responds with Hello World! (unit style)', async () => { | ||
describe('worker', () => { | ||
it('responds with 404 on index (unit style)', async () => { | ||
const request = new IncomingRequest('http://example.com'); | ||
// Create an empty context to pass to `worker.fetch()`. | ||
const ctx = createExecutionContext(); | ||
const response = await worker.fetch(request, env, ctx); | ||
// Wait for all `Promise`s passed to `ctx.waitUntil()` to settle before running test assertions | ||
await waitOnExecutionContext(ctx); | ||
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); | ||
expect(response.status).toBe(404); | ||
}); | ||
|
||
it('responds with Hello World! (integration style)', async () => { | ||
const response = await SELF.fetch('https://example.com'); | ||
expect(await response.text()).toMatchInlineSnapshot(`"Hello World!"`); | ||
it('rejects incorrect accounts', async () => { | ||
let response = await SELF.fetch('http://example.com/auth/register', { | ||
method: 'POST', | ||
body: JSON.stringify({ email: 'notanemail', password: 'password' }), | ||
}); | ||
expect(response.status).toBe(400); | ||
response = await SELF.fetch('http://example.com/auth/register', { | ||
method: 'POST', | ||
body: JSON.stringify({ email: '[email protected]', password: 'password' }), | ||
}); | ||
expect(response.status).toBe(400); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// test/index.spec.ts | ||
import { describe, it, expect } from 'vitest'; | ||
import { hashPassword, verifyPassword } from '../src/lib/bcrypt'; | ||
|
||
describe('bcrypt', () => { | ||
it('hashes and verifies passwords', async () => { | ||
const password = 'password'; | ||
const hashed = await hashPassword(password); | ||
expect(await verifyPassword(hashed, password)).toBe(true); | ||
}); | ||
}); |