WIP: auth user creation
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
CREATE TYPE "public"."role" AS ENUM('admin', 'visitor', 'user', 'volunteer');--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"email" varchar(255),
|
||||
"phone" varchar(255),
|
||||
"password" varchar(127),
|
||||
"image" text,
|
||||
"google_id" varchar(255),
|
||||
"role" "role" DEFAULT 'user' NOT NULL,
|
||||
"status" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone NOT NULL,
|
||||
"deleted_at" timestamp with time zone,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"id": "7f01d0a9-3f79-476c-b885-b83b3191bdf8",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "varchar(127)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"google_id": {
|
||||
"name": "google_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'user'"
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.role": {
|
||||
"name": "role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"admin",
|
||||
"visitor",
|
||||
"user",
|
||||
"volunteer"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1764313178750,
|
||||
"tag": "1764313178_flimsy_tarantula",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createHmac, timingSafeEqual } from 'crypto'
|
||||
import { createHmac, timingSafeEqual } from 'node:crypto'
|
||||
|
||||
/**
|
||||
* Canonicalizes an object by recursively removing 'signature' properties,
|
||||
@@ -8,6 +8,7 @@ import { createHmac, timingSafeEqual } from 'crypto'
|
||||
* converting Dates to ISO strings, BigInt to strings,
|
||||
* and non-finite numbers (NaN/Infinity/-Infinity) to null.
|
||||
* Throws on circular references or unsupported objects.
|
||||
*
|
||||
* @param data The data to canonicalize.
|
||||
* @returns The canonicalized, signature-free structure.
|
||||
*/
|
||||
@@ -96,6 +97,7 @@ export function canonicalizeObject<T = unknown>(data: T): unknown {
|
||||
|
||||
/**
|
||||
* Returns the canonical JSON string of the data.
|
||||
*
|
||||
* @param data The data to canonicalize.
|
||||
* @returns The canonical JSON string.
|
||||
*/
|
||||
@@ -105,6 +107,7 @@ export function canonicalize(data: unknown): string {
|
||||
|
||||
/**
|
||||
* Signs the data using HMAC-SHA256 and returns the signature as lowercase hex.
|
||||
*
|
||||
* @param data The data to sign.
|
||||
* @param secretKey The secret key. If not provided, uses process.env.SECRET_KEY.
|
||||
* @returns The signature.
|
||||
@@ -125,6 +128,7 @@ export function sign(data: unknown, secretKey?: string): string {
|
||||
|
||||
/**
|
||||
* Verifies the signature of the data using HMAC-SHA256 with constant-time comparison.
|
||||
*
|
||||
* @param data The data to verify.
|
||||
* @param signature The expected signature.
|
||||
* @param secretKey The secret key. If not provided, uses process.env.SECRET_KEY.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Clock skew window: ±5 minutes
|
||||
// const CLOCK_SKEW_MS = 5 * 60 * 1000
|
||||
|
||||
export const deviceGuard = () => ({
|
||||
beforeHandle(context: unknown) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { headers, set } = context as any
|
||||
// Validate X-API-Key
|
||||
const apiKey = headers['x-api-key']
|
||||
if (!apiKey || typeof apiKey !== 'string' || apiKey.trim() === '') {
|
||||
set.status = 401
|
||||
throw new Error('invalid_api_key')
|
||||
}
|
||||
|
||||
// Validate X-Timestamp
|
||||
// const timestampStr = headers['x-timestamp']
|
||||
// if (!timestampStr) {
|
||||
// set.status = 403
|
||||
// throw new Error('replay_attack_detected')
|
||||
// }
|
||||
// const timestamp = new Date(timestampStr)
|
||||
// if (isNaN(timestamp.getTime()) || timestampStr !== timestamp.toISOString()) {
|
||||
// set.status = 403
|
||||
// throw new Error('replay_attack_detected')
|
||||
// }
|
||||
// const now = new Date()
|
||||
// const diff = Math.abs(now.getTime() - timestamp.getTime())
|
||||
// if (diff > CLOCK_SKEW_MS) {
|
||||
// set.status = 403
|
||||
// throw new Error('replay_attack_detected')
|
||||
// }
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user