From 7350aac6f2431bbaab079bbacec14571c40dbc06 Mon Sep 17 00:00:00 2001 From: Mayowa Date: Thu, 25 Jun 2026 18:08:02 +0000 Subject: [PATCH] feat: strengthen device authentication, linking, session security, and key-bundle abuse protection --- apps/backend/src/db/schema.ts | 37 +++++ apps/backend/src/lib/jwt.ts | 1 + apps/backend/src/schemas/auth.schemas.ts | 10 ++ package-lock.json | 190 +++++++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 package-lock.json diff --git a/apps/backend/src/db/schema.ts b/apps/backend/src/db/schema.ts index 419c59f..15c3f74 100644 --- a/apps/backend/src/db/schema.ts +++ b/apps/backend/src/db/schema.ts @@ -19,6 +19,30 @@ export const wallets = pgTable('wallets', { createdAt: timestamp('created_at').notNull().defaultNow(), }); +export const devices = pgTable('devices', { + id: uuid('id').primaryKey().defaultRandom(), + userId: uuid('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + deviceId: text('device_id').notNull(), + deviceName: text('device_name').notNull(), + platform: text('platform').notNull(), + identityPublicKey: text('identity_public_key').notNull(), + registrationId: text('registration_id'), + isRevoked: boolean('is_revoked').notNull().default(false), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), +}); + +export const devicePrekeys = pgTable('device_prekeys', { + id: uuid('id').primaryKey().defaultRandom(), + deviceId: uuid('device_id') + .notNull() + .references(() => devices.id, { onDelete: 'cascade' }), + prekey: text('prekey').notNull(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + // ─── Conversations ──────────────────────────────────────────────────────────── export const conversationTypeEnum = pgEnum('conversation_type', ['dm', 'group']); @@ -137,6 +161,15 @@ export const tokenTransfersRelations = relations(tokenTransfers, ({ one }) => ({ }), })); +export const devicesRelations = relations(devices, ({ one, many }) => ({ + user: one(users, { fields: [devices.userId], references: [users.id] }), + prekeys: many(devicePrekeys), +})); + +export const devicePrekeysRelations = relations(devicePrekeys, ({ one }) => ({ + device: one(devices, { fields: [devicePrekeys.deviceId], references: [devices.id] }), +})); + // ─── Types ──────────────────────────────────────────────────────────────────── export type User = typeof users.$inferSelect; @@ -150,3 +183,7 @@ export type Message = typeof messages.$inferSelect; export type NewMessage = typeof messages.$inferInsert; export type TokenTransfer = typeof tokenTransfers.$inferSelect; export type NewTokenTransfer = typeof tokenTransfers.$inferInsert; +export type Device = typeof devices.$inferSelect; +export type NewDevice = typeof devices.$inferInsert; +export type DevicePrekey = typeof devicePrekeys.$inferSelect; +export type NewDevicePrekey = typeof devicePrekeys.$inferInsert; diff --git a/apps/backend/src/lib/jwt.ts b/apps/backend/src/lib/jwt.ts index b9bf3ba..c1f143c 100644 --- a/apps/backend/src/lib/jwt.ts +++ b/apps/backend/src/lib/jwt.ts @@ -11,6 +11,7 @@ const JWT_SECRET: string = SECRET; export interface JwtPayload { userId: string; walletAddress: string; + deviceId?: string; } export function signToken(payload: JwtPayload): string { diff --git a/apps/backend/src/schemas/auth.schemas.ts b/apps/backend/src/schemas/auth.schemas.ts index c22136b..a8c1848 100644 --- a/apps/backend/src/schemas/auth.schemas.ts +++ b/apps/backend/src/schemas/auth.schemas.ts @@ -4,11 +4,21 @@ export const ChallengeSchema = z.object({ walletAddress: z.string().min(1, 'walletAddress is required'), }); +export const DeviceSchema = z.object({ + deviceId: z.string().min(1, 'deviceId is required'), + deviceName: z.string().min(1, 'deviceName is required'), + platform: z.string().min(1, 'platform is required'), + identityPublicKey: z.string().min(1, 'identityPublicKey is required'), + registrationId: z.string().optional(), +}); + export const VerifySchema = z.object({ walletAddress: z.string().min(1, 'walletAddress is required'), signature: z.string().min(1, 'signature is required'), nonce: z.string().min(1, 'nonce is required'), + device: DeviceSchema.optional(), }); export type ChallengeBody = z.infer; +export type DeviceBody = z.infer; export type VerifyBody = z.infer; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..242d6ee --- /dev/null +++ b/package-lock.json @@ -0,0 +1,190 @@ +{ + "name": "clicked", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "clicked", + "version": "1.0.0", + "devDependencies": { + "prettier": "latest", + "turbo": "latest" + } + }, + "node_modules/@turbo/darwin-64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.10.0.tgz", + "integrity": "sha512-EwvHThXzpY0KGd1/NAmuewI5D+aVa3Rl/OlxE36yfjUKb/+ySrfJrSlEFt8aD1OXwnnaHnQnPKHFndor0Zxlsg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@turbo/darwin-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.10.0.tgz", + "integrity": "sha512-9d2fTyyG0lf5Wq1bwJA9qUaeecViMkLcdctWaMMmCkxZ/JqypmqOwK3W6vmejeKVgkr06gSoiX8bD+xN5Jpxcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@turbo/linux-64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/linux-64/-/linux-64-2.10.0.tgz", + "integrity": "sha512-sZBtjMuufitanjzi6UssoUpJMnnPlLMcdcJj3m3ptNsSq31Xh7MnjhwA5nWvLDTfEFg8GPcbYFXMo8vSdKRfqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@turbo/linux-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/linux-arm64/-/linux-arm64-2.10.0.tgz", + "integrity": "sha512-vkq/Z8R+1DQ+kifWFa810IjRy2NNBVvha3cg9sWA3nFh6nnGrHSMnnJKrzH7c/No9kq4Jb55Ru44YKsCSBgrKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@turbo/windows-64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/windows-64/-/windows-64-2.10.0.tgz", + "integrity": "sha512-CRUEguLWxFQHptYZS7HjPhNhAFawfea07iR+xAQ5e4klgLrPCMdexBkXwSCwOxqTFknJ7RZFN3gOaADsw+Gttg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@turbo/windows-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/windows-arm64/-/windows-arm64-2.10.0.tgz", + "integrity": "sha512-dVHGaf9F8twzgibcBqKoADT/LLqf9++jDb+hq/LPWWaOmRpp4M+/pVOm7vy4z9D++xg8eaxWLT0+wQxFwhYu9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/turbo": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.10.0.tgz", + "integrity": "sha512-o016H9PPtuH2deb3mh3Vci3Avfi9UYgM/RONQisY7HnloupP0IFSbFS3gFYJgFJP8nwBrByHWFQIDa8T2zIXPw==", + "dev": true, + "bin": { + "turbo": "bin/turbo" + }, + "optionalDependencies": { + "@turbo/darwin-64": "2.10.0", + "@turbo/darwin-arm64": "2.10.0", + "@turbo/linux-64": "2.10.0", + "@turbo/linux-arm64": "2.10.0", + "@turbo/windows-64": "2.10.0", + "@turbo/windows-arm64": "2.10.0" + } + } + }, + "dependencies": { + "@turbo/darwin-64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.10.0.tgz", + "integrity": "sha512-EwvHThXzpY0KGd1/NAmuewI5D+aVa3Rl/OlxE36yfjUKb/+ySrfJrSlEFt8aD1OXwnnaHnQnPKHFndor0Zxlsg==", + "dev": true, + "optional": true + }, + "@turbo/darwin-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.10.0.tgz", + "integrity": "sha512-9d2fTyyG0lf5Wq1bwJA9qUaeecViMkLcdctWaMMmCkxZ/JqypmqOwK3W6vmejeKVgkr06gSoiX8bD+xN5Jpxcg==", + "dev": true, + "optional": true + }, + "@turbo/linux-64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/linux-64/-/linux-64-2.10.0.tgz", + "integrity": "sha512-sZBtjMuufitanjzi6UssoUpJMnnPlLMcdcJj3m3ptNsSq31Xh7MnjhwA5nWvLDTfEFg8GPcbYFXMo8vSdKRfqQ==", + "dev": true, + "optional": true + }, + "@turbo/linux-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/linux-arm64/-/linux-arm64-2.10.0.tgz", + "integrity": "sha512-vkq/Z8R+1DQ+kifWFa810IjRy2NNBVvha3cg9sWA3nFh6nnGrHSMnnJKrzH7c/No9kq4Jb55Ru44YKsCSBgrKg==", + "dev": true, + "optional": true + }, + "@turbo/windows-64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/windows-64/-/windows-64-2.10.0.tgz", + "integrity": "sha512-CRUEguLWxFQHptYZS7HjPhNhAFawfea07iR+xAQ5e4klgLrPCMdexBkXwSCwOxqTFknJ7RZFN3gOaADsw+Gttg==", + "dev": true, + "optional": true + }, + "@turbo/windows-arm64": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@turbo/windows-arm64/-/windows-arm64-2.10.0.tgz", + "integrity": "sha512-dVHGaf9F8twzgibcBqKoADT/LLqf9++jDb+hq/LPWWaOmRpp4M+/pVOm7vy4z9D++xg8eaxWLT0+wQxFwhYu9A==", + "dev": true, + "optional": true + }, + "prettier": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "dev": true + }, + "turbo": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.10.0.tgz", + "integrity": "sha512-o016H9PPtuH2deb3mh3Vci3Avfi9UYgM/RONQisY7HnloupP0IFSbFS3gFYJgFJP8nwBrByHWFQIDa8T2zIXPw==", + "dev": true, + "requires": { + "@turbo/darwin-64": "2.10.0", + "@turbo/darwin-arm64": "2.10.0", + "@turbo/linux-64": "2.10.0", + "@turbo/linux-arm64": "2.10.0", + "@turbo/windows-64": "2.10.0", + "@turbo/windows-arm64": "2.10.0" + } + } + } +}