From c04121613e8dcf3e9b587c2bac4f0ce3cc8b3033 Mon Sep 17 00:00:00 2001 From: Redm4x <2829180+Redm4x@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:15:41 -0500 Subject: [PATCH] feat(user): add last ip, user-agent and fingerprint on user settings --- .../api/drizzle/0007_lethal_masked_marvel.sql | 3 + apps/api/drizzle/meta/0007_snapshot.json | 304 ++++++++++++++++++ apps/api/drizzle/meta/_journal.json | 7 + apps/api/package.json | 10 +- apps/api/src/app.ts | 2 + .../src/middlewares/clientInfoMiddleware.ts | 42 +++ apps/api/src/routers/userRouter.ts | 8 +- apps/api/src/routes/v1/predictedBlockDate.ts | 16 +- apps/api/src/routes/v1/predictedDateHeight.ts | 16 +- .../src/routes/v1/providers/deployments.ts | 6 +- apps/api/src/routes/v1/transactions/list.ts | 2 +- apps/api/src/services/db/userDataService.ts | 36 ++- .../user/controllers/user/user.controller.ts | 15 +- .../user/model-schemas/user/user.schema.ts | 3 + .../create-anonymous-user.router.ts | 3 +- .../hono-middleware.ts | 2 +- package-lock.json | 63 ++-- .../database/dbSchemas/user/userSetting.ts | 3 + 18 files changed, 478 insertions(+), 63 deletions(-) create mode 100644 apps/api/drizzle/0007_lethal_masked_marvel.sql create mode 100644 apps/api/drizzle/meta/0007_snapshot.json create mode 100644 apps/api/src/middlewares/clientInfoMiddleware.ts diff --git a/apps/api/drizzle/0007_lethal_masked_marvel.sql b/apps/api/drizzle/0007_lethal_masked_marvel.sql new file mode 100644 index 000000000..5fa8f7c4f --- /dev/null +++ b/apps/api/drizzle/0007_lethal_masked_marvel.sql @@ -0,0 +1,3 @@ +ALTER TABLE "userSetting" ADD COLUMN "last_ip" varchar(255);--> statement-breakpoint +ALTER TABLE "userSetting" ADD COLUMN "last_user_agent" varchar(255);--> statement-breakpoint +ALTER TABLE "userSetting" ADD COLUMN "last_fingerprint" varchar(255); \ No newline at end of file diff --git a/apps/api/drizzle/meta/0007_snapshot.json b/apps/api/drizzle/meta/0007_snapshot.json new file mode 100644 index 000000000..65a00539d --- /dev/null +++ b/apps/api/drizzle/meta/0007_snapshot.json @@ -0,0 +1,304 @@ +{ + "id": "7b2b63b9-6539-46b0-ae35-88893697cfde", + "prevId": "d83b4940-34c1-400c-98d9-5a1eb935fe5e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.user_wallets": { + "name": "user_wallets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "deployment_allowance": { + "name": "deployment_allowance", + "type": "numeric(20, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0.00'" + }, + "fee_allowance": { + "name": "fee_allowance", + "type": "numeric(20, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0.00'" + }, + "trial": { + "name": "trial", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_wallets_user_id_userSetting_id_fk": { + "name": "user_wallets_user_id_userSetting_id_fk", + "tableFrom": "user_wallets", + "tableTo": "userSetting", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_wallets_user_id_unique": { + "name": "user_wallets_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "user_wallets_address_unique": { + "name": "user_wallets_address_unique", + "nullsNotDistinct": false, + "columns": [ + "address" + ] + } + } + }, + "public.checkout_sessions": { + "name": "checkout_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "checkout_sessions_user_id_userSetting_id_fk": { + "name": "checkout_sessions_user_id_userSetting_id_fk", + "tableFrom": "checkout_sessions", + "tableTo": "userSetting", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "checkout_sessions_session_id_unique": { + "name": "checkout_sessions_session_id_unique", + "nullsNotDistinct": false, + "columns": [ + "session_id" + ] + } + } + }, + "public.userSetting": { + "name": "userSetting", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subscribedToNewsletter": { + "name": "subscribedToNewsletter", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "youtubeUsername": { + "name": "youtubeUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "twitterUsername": { + "name": "twitterUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "githubUsername": { + "name": "githubUsername", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "last_ip": { + "name": "last_ip", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_user_agent": { + "name": "last_user_agent", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_fingerprint": { + "name": "last_fingerprint", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "userSetting_userId_unique": { + "name": "userSetting_userId_unique", + "nullsNotDistinct": false, + "columns": [ + "userId" + ] + }, + "userSetting_username_unique": { + "name": "userSetting_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/api/drizzle/meta/_journal.json b/apps/api/drizzle/meta/_journal.json index da0149e47..51273d550 100644 --- a/apps/api/drizzle/meta/_journal.json +++ b/apps/api/drizzle/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1731579448104, "tag": "0006_skinny_stingray", "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1732626493525, + "tag": "0007_lethal_masked_marvel", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/api/package.json b/apps/api/package.json index fef0b2ec4..ed74f529b 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -49,10 +49,10 @@ "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", "@dotenvx/dotenvx": "^1.9.0", - "@hono/node-server": "1.4.0", - "@hono/sentry": "^1.0.0", - "@hono/swagger-ui": "0.2.1", - "@hono/zod-openapi": "0.9.5", + "@hono/node-server": "1.13.7", + "@hono/sentry": "^1.2.0", + "@hono/swagger-ui": "0.4.1", + "@hono/zod-openapi": "0.18.0", "@octokit/rest": "^18.12.0", "@opentelemetry/instrumentation": "^0.54.2", "@opentelemetry/instrumentation-http": "^0.54.2", @@ -71,7 +71,7 @@ "dotenv-expand": "^11.0.6", "drizzle-orm": "^0.31.2", "exponential-backoff": "^3.1.1", - "hono": "3.12.0", + "hono": "4.6.12", "http-assert": "^1.5.0", "http-errors": "^2.0.0", "human-interval": "^2.0.1", diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 054fb0117..24dce1668 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -16,6 +16,7 @@ import { RequestContextInterceptor } from "@src/core/services/request-context-in import { HonoInterceptor } from "@src/core/types/hono-interceptor.type"; import packageJson from "../package.json"; import { chainDb, syncUserSchema, userDb } from "./db/dbConnection"; +import { clientInfoMiddleware } from "./middlewares/clientInfoMiddleware"; import { apiRouter } from "./routers/apiRouter"; import { dashboardRouter } from "./routers/dashboardRouter"; import { internalRouter } from "./routers/internalRouter"; @@ -49,6 +50,7 @@ const scheduler = new Scheduler({ appHono.use(container.resolve(HttpLoggerService).intercept()); appHono.use(container.resolve(RequestContextInterceptor).intercept()); appHono.use(container.resolve(AuthInterceptor).intercept()); +appHono.use(clientInfoMiddleware); appHono.use("*", async (c: Context, next: Next) => { const { sentry } = await import("@hono/sentry"); return sentry({ diff --git a/apps/api/src/middlewares/clientInfoMiddleware.ts b/apps/api/src/middlewares/clientInfoMiddleware.ts new file mode 100644 index 000000000..9f1f2e808 --- /dev/null +++ b/apps/api/src/middlewares/clientInfoMiddleware.ts @@ -0,0 +1,42 @@ +import { getConnInfo } from "@hono/node-server/conninfo"; +import crypto from "crypto"; +import { Context, Next } from "hono"; +import { createMiddleware } from "hono/factory"; + +import { getSentry } from "@src/core/providers/sentry.provider"; + +export type ClientInfoContextVariables = { + clientInfo: + | { + ip: string; + userAgent: string | undefined; + fingerprint: string | undefined; + } + | undefined; +}; + +export const clientInfoMiddleware = createMiddleware<{ + Variables: ClientInfoContextVariables; +}>(async (c: Context, next: Next) => { + try { + const info = getConnInfo(c); + const ip = c.req.header("cf-connecting-ip") || info.remote.address; + const userAgent = c.req.header("User-Agent"); + const accept = c.req.header("Accept"); + const acceptEncoding = c.req.header("Accept-Encoding"); + const acceptLanguage = c.req.header("Accept-Language"); + + const fingerprintSource = [ip, userAgent, accept, acceptEncoding, acceptLanguage].join(""); + const fingerprint = crypto.createHash("sha256").update(fingerprintSource).digest("hex"); + + c.set("clientInfo", { + ip: ip, + userAgent: userAgent, + fingerprint: fingerprint + }); + } catch (err) { + getSentry().captureException(err); + } finally { + await next(); + } +}); diff --git a/apps/api/src/routers/userRouter.ts b/apps/api/src/routers/userRouter.ts index 970f797f8..5b28d28fe 100644 --- a/apps/api/src/routers/userRouter.ts +++ b/apps/api/src/routers/userRouter.ts @@ -4,6 +4,7 @@ import { container } from "tsyringe"; import * as uuid from "uuid"; import { AuthTokenService } from "@src/auth/services/auth-token/auth-token.service"; +import { ClientInfoContextVariables } from "@src/middlewares/clientInfoMiddleware"; import { getCurrentUserId, optionalUserMiddleware, requiredUserMiddleware } from "@src/middlewares/userMiddleware"; import { addTemplateFavorite, @@ -19,7 +20,7 @@ import { checkUsernameAvailable, getSettingsOrInit, getUserByUsername, subscribe export const userRouter = new Hono(); -const userRequiredRouter = new Hono(); +const userRequiredRouter = new Hono<{ Variables: ClientInfoContextVariables }>(); userRequiredRouter.use("*", requiredUserMiddleware); const userOptionalRouter = new Hono(); @@ -47,7 +48,10 @@ userRequiredRouter.post("/tokenInfo", async c => { wantedUsername, email: email, emailVerified: !!emailVerified, - subscribedToNewsletter: subscribedToNewsletter + subscribedToNewsletter: subscribedToNewsletter, + ip: c.var.clientInfo?.ip, + userAgent: c.var.clientInfo?.userAgent, + fingerprint: c.var.clientInfo?.fingerprint }); return c.json(settings); diff --git a/apps/api/src/routes/v1/predictedBlockDate.ts b/apps/api/src/routes/v1/predictedBlockDate.ts index 0016fb5ee..b88b5e56b 100644 --- a/apps/api/src/routes/v1/predictedBlockDate.ts +++ b/apps/api/src/routes/v1/predictedBlockDate.ts @@ -1,5 +1,4 @@ -import { createRoute, OpenAPIHono } from "@hono/zod-openapi"; -import { z } from "zod"; +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { getPredictedBlockDate } from "@src/services/db/blocksService"; @@ -67,9 +66,12 @@ export default new OpenAPIHono().openapi(route, async c => { const date = await getPredictedBlockDate(height, blockWindow); - return c.json({ - predictedDate: date, - height: height, - blockWindow: blockWindow - }); + return c.json( + { + predictedDate: date, + height: height, + blockWindow: blockWindow + }, + 200 + ); }); diff --git a/apps/api/src/routes/v1/predictedDateHeight.ts b/apps/api/src/routes/v1/predictedDateHeight.ts index 44403c3ad..14344bd38 100644 --- a/apps/api/src/routes/v1/predictedDateHeight.ts +++ b/apps/api/src/routes/v1/predictedDateHeight.ts @@ -1,5 +1,4 @@ -import { createRoute, OpenAPIHono } from "@hono/zod-openapi"; -import { z } from "zod"; +import { createRoute, OpenAPIHono, z } from "@hono/zod-openapi"; import { getPredictedDateHeight } from "@src/services/db/blocksService"; @@ -68,9 +67,12 @@ export default new OpenAPIHono().openapi(route, async c => { const date = new Date(timestamp * 1000); const height = await getPredictedDateHeight(date, blockWindow); - return c.json({ - predictedHeight: height, - date: date, - blockWindow: blockWindow - }); + return c.json( + { + predictedHeight: height, + date: date, + blockWindow: blockWindow + }, + 200 + ); }); diff --git a/apps/api/src/routes/v1/providers/deployments.ts b/apps/api/src/routes/v1/providers/deployments.ts index 036a2326f..9d7319b0e 100644 --- a/apps/api/src/routes/v1/providers/deployments.ts +++ b/apps/api/src/routes/v1/providers/deployments.ts @@ -94,14 +94,14 @@ const route = createRoute({ export default new OpenAPIHono().openapi(route, async c => { const skip = parseInt(c.req.valid("param").skip); const limit = Math.min(maxLimit, parseInt(c.req.valid("param").limit)); - const statusParam = c.req.query("status") as "active" | "closed" | undefined; + const statusParam = c.req.query("status"); // TODO: Validate skip/limit if (statusParam && statusParam !== "active" && statusParam !== "closed") { return c.text(`Invalid status filter: "${statusParam}". Valid values are "active" and "closed".`, 400); } - const deploymentCountQuery = getProviderDeploymentsCount(c.req.valid("param").provider, statusParam); - const deploymentsQuery = getProviderDeployments(c.req.valid("param").provider, skip, limit, statusParam); + const deploymentCountQuery = getProviderDeploymentsCount(c.req.valid("param").provider, statusParam as "active" | "closed"); + const deploymentsQuery = getProviderDeployments(c.req.valid("param").provider, skip, limit, statusParam as "active" | "closed"); const [deploymentCount, deployments] = await Promise.all([deploymentCountQuery, deploymentsQuery]); diff --git a/apps/api/src/routes/v1/transactions/list.ts b/apps/api/src/routes/v1/transactions/list.ts index 11156e8a6..d1f00145c 100644 --- a/apps/api/src/routes/v1/transactions/list.ts +++ b/apps/api/src/routes/v1/transactions/list.ts @@ -76,5 +76,5 @@ export default new OpenAPIHono().openapi(route, async c => { const transactions = await getTransactions(limit || defaultLimit); - return c.json(transactions); + return c.json(transactions, 200); }); diff --git a/apps/api/src/services/db/userDataService.ts b/apps/api/src/services/db/userDataService.ts index 52f9e35f9..a73802a84 100644 --- a/apps/api/src/services/db/userDataService.ts +++ b/apps/api/src/services/db/userDataService.ts @@ -71,9 +71,22 @@ type UserInput = { email: string; emailVerified: boolean; subscribedToNewsletter: boolean; + ip?: string; + userAgent?: string; + fingerprint?: string; }; -export async function getSettingsOrInit({ anonymousUserId, userId, wantedUsername, email, emailVerified, subscribedToNewsletter }: UserInput) { +export async function getSettingsOrInit({ + anonymousUserId, + userId, + wantedUsername, + email, + emailVerified, + subscribedToNewsletter, + ip, + userAgent, + fingerprint +}: UserInput) { let userSettings: UserSetting; let isAnonymous = false; @@ -86,7 +99,10 @@ export async function getSettingsOrInit({ anonymousUserId, userId, wantedUsernam email: email, emailVerified: emailVerified, stripeCustomerId: null, - subscribedToNewsletter: subscribedToNewsletter + subscribedToNewsletter, + lastIp: ip, + lastUserAgent: userAgent, + lastFingerprint: fingerprint }, { where: { id: anonymousUserId, userId: null }, returning: ["*"] } ); @@ -125,14 +141,26 @@ export async function getSettingsOrInit({ anonymousUserId, userId, wantedUsernam email: email, emailVerified: emailVerified, stripeCustomerId: null, - subscribedToNewsletter: subscribedToNewsletter + subscribedToNewsletter, + lastIp: ip, + lastUserAgent: userAgent, + lastFingerprint: fingerprint }); logger.info({ event: "USER_REGISTERED", userId }); } - if (userSettings.email !== email || userSettings.emailVerified !== emailVerified) { + if ( + userSettings.email !== email || + userSettings.emailVerified !== emailVerified || + userSettings.lastIp !== ip || + userSettings.lastUserAgent !== userAgent || + userSettings.lastFingerprint !== fingerprint + ) { userSettings.email = email; userSettings.emailVerified = emailVerified; + userSettings.lastIp = ip || userSettings.lastIp; + userSettings.lastUserAgent = userAgent || userSettings.lastUserAgent; + userSettings.lastFingerprint = fingerprint || userSettings.lastFingerprint; await userSettings.save(); } diff --git a/apps/api/src/user/controllers/user/user.controller.ts b/apps/api/src/user/controllers/user/user.controller.ts index 42b1b7ec2..f99bda70a 100644 --- a/apps/api/src/user/controllers/user/user.controller.ts +++ b/apps/api/src/user/controllers/user/user.controller.ts @@ -10,6 +10,8 @@ import { StaleAnonymousUsersCleanerOptions, StaleAnonymousUsersCleanerService } from "@src/user/services/stale-anonymous-users-cleaner/stale-anonymous-users-cleaner.service"; +import { ExecutionContextService } from "@src/core/services/execution-context/execution-context.service"; +import { Context } from "hono"; @singleton() export class UserController { @@ -17,11 +19,20 @@ export class UserController { private readonly userRepository: UserRepository, private readonly authService: AuthService, private readonly anonymousUserAuthService: AuthTokenService, - private readonly staleAnonymousUsersCleanerService: StaleAnonymousUsersCleanerService + private readonly staleAnonymousUsersCleanerService: StaleAnonymousUsersCleanerService, + private readonly executionContextService: ExecutionContextService ) {} + get httpContext(): Context { + return this.executionContextService.get("HTTP_CONTEXT"); + } + async create(): Promise { - const user = await this.userRepository.create(); + const user = await this.userRepository.create({ + lastIp: this.httpContext.var.clientInfo?.ip, + lastUserAgent: this.httpContext.var.clientInfo?.userAgent, + lastFingerprint: this.httpContext.var.clientInfo?.fingerprint + }); return { data: user, token: this.anonymousUserAuthService.signTokenFor({ id: user.id }) diff --git a/apps/api/src/user/model-schemas/user/user.schema.ts b/apps/api/src/user/model-schemas/user/user.schema.ts index 7b16c1881..1855b8099 100644 --- a/apps/api/src/user/model-schemas/user/user.schema.ts +++ b/apps/api/src/user/model-schemas/user/user.schema.ts @@ -17,5 +17,8 @@ export const Users = pgTable("userSetting", { twitterUsername: varchar("twitterUsername", { length: 255 }), githubUsername: varchar("githubUsername", { length: 255 }), lastActiveAt: timestamp("last_active_at").defaultNow(), + lastIp: varchar("last_ip", { length: 255 }), + lastUserAgent: varchar("last_user_agent", { length: 255 }), + lastFingerprint: varchar("last_fingerprint", { length: 255 }), createdAt: timestamp("created_at").defaultNow() }); diff --git a/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts b/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts index 6e692b06a..eaaf06f3a 100644 --- a/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts +++ b/apps/api/src/user/routes/create-anonymous-user/create-anonymous-user.router.ts @@ -1,6 +1,7 @@ import { createRoute, OpenAPIHono } from "@hono/zod-openapi"; import { container } from "tsyringe"; +import { ClientInfoContextVariables } from "@src/middlewares/clientInfoMiddleware"; import { UserController } from "@src/user/controllers/user/user.controller"; import { AnonymousUserResponseOutputSchema } from "@src/user/schemas/user.schema"; @@ -23,7 +24,7 @@ const route = createRoute({ } } }); -export const createAnonymousUserRouter = new OpenAPIHono(); +export const createAnonymousUserRouter = new OpenAPIHono<{ Variables: ClientInfoContextVariables }>(); createAnonymousUserRouter.openapi(route, async function routeCreateUser(c) { return c.json(await container.resolve(UserController).create(), 200); diff --git a/apps/api/src/verify-rsa-jwt-cloudflare-worker-main/hono-middleware.ts b/apps/api/src/verify-rsa-jwt-cloudflare-worker-main/hono-middleware.ts index 4939b8c0c..1df4136f8 100644 --- a/apps/api/src/verify-rsa-jwt-cloudflare-worker-main/hono-middleware.ts +++ b/apps/api/src/verify-rsa-jwt-cloudflare-worker-main/hono-middleware.ts @@ -16,7 +16,7 @@ const PAYLOAD_KEY = "verifyRsaJwtPayload"; export function verifyRsaJwt(config?: VerifyRsaJwtConfig): MiddlewareHandler { return async (ctx: Context, next) => { try { - const jwtToken = ctx.req.headers.get("Authorization")?.replace(/Bearer\s+/i, ""); + const jwtToken = ctx.req.header("Authorization")?.replace(/Bearer\s+/i, ""); if (!jwtToken || jwtToken.length === 0) { throw new Error("JWT token not found in Authorization header"); } diff --git a/package-lock.json b/package-lock.json index 52cf74850..24d3abb03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ }, "apps/api": { "name": "@akashnetwork/console-api", - "version": "2.41.1", + "version": "2.43.0", "license": "Apache-2.0", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", @@ -49,10 +49,10 @@ "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", "@dotenvx/dotenvx": "^1.9.0", - "@hono/node-server": "1.4.0", - "@hono/sentry": "^1.0.0", - "@hono/swagger-ui": "0.2.1", - "@hono/zod-openapi": "0.9.5", + "@hono/node-server": "1.13.7", + "@hono/sentry": "^1.2.0", + "@hono/swagger-ui": "0.4.1", + "@hono/zod-openapi": "0.18.0", "@octokit/rest": "^18.12.0", "@opentelemetry/instrumentation": "^0.54.2", "@opentelemetry/instrumentation-http": "^0.54.2", @@ -71,7 +71,7 @@ "dotenv-expand": "^11.0.6", "drizzle-orm": "^0.31.2", "exponential-backoff": "^3.1.1", - "hono": "3.12.0", + "hono": "4.6.12", "http-assert": "^1.5.0", "http-errors": "^2.0.0", "human-interval": "^2.0.1", @@ -227,7 +227,7 @@ }, "apps/deploy-web": { "name": "@akashnetwork/console-web", - "version": "2.25.4", + "version": "2.27.0", "license": "Apache-2.0", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", @@ -946,9 +946,9 @@ } }, "node_modules/@asteasolutions/zod-to-openapi": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-5.5.0.tgz", - "integrity": "sha512-d5HwrvM6dOKr3XdeF+DmashGvfEc+1oiEfbscugsiwSTrFtuMa7ETpW9sTNnVgn+hJaz+PRxPQUYD7q9/5dUig==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@asteasolutions/zod-to-openapi/-/zod-to-openapi-7.3.0.tgz", + "integrity": "sha512-7tE/r1gXwMIvGnXVUdIqUhCU1RevEFC4Jk6Bussa0fk1ecbnnINkZzj1EOAJyE/M3AI25DnHT/zKQL1/FPFi8Q==", "dependencies": { "openapi3-ts": "^4.1.2" }, @@ -6284,11 +6284,14 @@ } }, "node_modules/@hono/node-server": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.4.0.tgz", - "integrity": "sha512-bhDkhldW7w9VgjrX0gG1vJ2YyvTxFWd5WG9nHjSR4UauhVECQZC3qy7mVVuQ054I5NWhKttHfKzYfoPzmUzAjw==", + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.7.tgz", + "integrity": "sha512-kTfUMsoloVKtRA2fLiGSd9qBddmru9KadNyhJCwgKBxTiNkaAJEwkVN9KV/rS4HtmmNRtUh6P+YpmjRMl0d9vQ==", "engines": { "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" } }, "node_modules/@hono/sentry": { @@ -6303,33 +6306,33 @@ } }, "node_modules/@hono/swagger-ui": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@hono/swagger-ui/-/swagger-ui-0.2.1.tgz", - "integrity": "sha512-wBxVMRe3/v8xH4o6icmwztiIq0DG0s7+jHVMHVUAoFFCWEQNL2iskMmQtrhSDtsFmBZUeUFQUaaJ6Ir6DOmHLA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@hono/swagger-ui/-/swagger-ui-0.4.1.tgz", + "integrity": "sha512-kPaJatHffeYQ3yVkHo878hCqwfapqx54FczJVJ+eRWt8J4biyVVMIdCAJb6MyA8bcnHUoTmUpPc7OJAV1VTg2g==", "peerDependencies": { "hono": "*" } }, "node_modules/@hono/zod-openapi": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@hono/zod-openapi/-/zod-openapi-0.9.5.tgz", - "integrity": "sha512-Px8QEIr5RyDeHQn51Ihnwtl//foFvd/F6yBq6o/3pVMUpoV6X4JZ+srgUrOX2VRrInpLVEP/NaroXlfvarwIxA==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@hono/zod-openapi/-/zod-openapi-0.18.0.tgz", + "integrity": "sha512-MNdFSbACkEq1txteKsBrVB0Mnil0zd5urOrP8eti6kUDI95CKVws+vjHQWNddRqmqlBHa5kFAcVXSInHFmcYGQ==", "dependencies": { - "@asteasolutions/zod-to-openapi": "^5.5.0", - "@hono/zod-validator": "^0.1.11" + "@asteasolutions/zod-to-openapi": "^7.1.0", + "@hono/zod-validator": "^0.4.1" }, "engines": { "node": ">=16.0.0" }, "peerDependencies": { - "hono": ">=3.11.3", + "hono": ">=4.3.6", "zod": "3.*" } }, "node_modules/@hono/zod-validator": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.1.11.tgz", - "integrity": "sha512-PQXeHUP0+36qpRt8yfeD7N2jbK3ETlGvSN6dMof/HwUC/APRokQRjpXZm4rrlG71Ft0aWE01+Bm4XejqPie5Uw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@hono/zod-validator/-/zod-validator-0.4.2.tgz", + "integrity": "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g==", "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" @@ -23491,11 +23494,11 @@ "license": "MIT" }, "node_modules/hono": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/hono/-/hono-3.12.0.tgz", - "integrity": "sha512-UPEtZuLY7Wo7g0mqKWSOjLFdT8t7wJ60IYEcxKl3AQNU4u+R2QqU2fJMPmSu24C+/ag20Z8mOTQOErZzK4DMvA==", + "version": "4.6.12", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.6.12.tgz", + "integrity": "sha512-eHtf4kSDNw6VVrdbd5IQi16r22m3s7mWPLd7xOMhg1a/Yyb1A0qpUFq8xYMX4FMuDe1nTKeMX5rTx7Nmw+a+Ag==", "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" } }, "node_modules/hosted-git-info": { @@ -39304,7 +39307,7 @@ }, "packages/http-sdk": { "name": "@akashnetwork/http-sdk", - "version": "1.0.8", + "version": "1.1.0", "license": "Apache-2.0", "dependencies": { "axios": "^1.7.2", diff --git a/packages/database/dbSchemas/user/userSetting.ts b/packages/database/dbSchemas/user/userSetting.ts index 2dc4e695e..a2060ed9b 100644 --- a/packages/database/dbSchemas/user/userSetting.ts +++ b/packages/database/dbSchemas/user/userSetting.ts @@ -23,6 +23,9 @@ export class UserSetting extends Model { @Column youtubeUsername?: string; @Column twitterUsername?: string; @Column githubUsername?: string; + @Column({ field: "last_ip" }) lastIp?: string; + @Column({ field: "last_user_agent" }) lastUserAgent?: string; + @Column({ field: "last_fingerprint" }) lastFingerprint?: string; @HasMany(() => Template, { foreignKey: "userId", sourceKey: "userId" }) templates: Template[]; }