|
1 | 1 | import { OrganizationId, UnauthorizedError, UserId } from "@domain/shared" |
2 | | -import { |
3 | | - type PostgresDb, |
4 | | - createApiKeyPostgresRepository, |
5 | | - createMembershipPostgresRepository, |
6 | | -} from "@platform/db-postgres" |
| 2 | +import { type PostgresDb, createApiKeyPostgresRepository } from "@platform/db-postgres" |
7 | 3 | import { hashToken } from "@repo/utils" |
8 | 4 | import { Effect, Option } from "effect" |
9 | 5 | import type { Context, MiddlewareHandler, Next } from "hono" |
10 | | -import { getAdminPostgresClient, getBetterAuth } from "../clients.ts" |
| 6 | +import { getAdminPostgresClient } from "../clients.ts" |
11 | 7 | import type { AuthContext } from "../types.ts" |
12 | 8 | import { createTouchBuffer } from "./touch-buffer.ts" |
13 | 9 |
|
@@ -160,27 +156,6 @@ const enforceMinimumTime = (startTime: number, minMs: number): Effect.Effect<voi |
160 | 156 | return Effect.void |
161 | 157 | } |
162 | 158 |
|
163 | | -/** |
164 | | - * Validate that a user is a member of the specified organization. |
165 | | - * Returns true if membership is valid, false otherwise. |
166 | | - */ |
167 | | -const validateOrganizationMembership = ( |
168 | | - c: Context, |
169 | | - userId: string, |
170 | | - organizationId: string, |
171 | | -): Effect.Effect<boolean, never> => { |
172 | | - const membershipRepository = createMembershipPostgresRepository(c.get("db")) |
173 | | - return membershipRepository.isMember(OrganizationId(organizationId), userId).pipe(Effect.orDie) |
174 | | -} |
175 | | - |
176 | | -/** |
177 | | - * Extract API key token from request headers. |
178 | | - */ |
179 | | -const extractApiKeyToken = (c: Context): string | undefined => { |
180 | | - const apiKeyHeader = c.req.header("X-API-Key") |
181 | | - return apiKeyHeader || undefined |
182 | | -} |
183 | | - |
184 | 159 | const extractBearerToken = (c: Context): string | undefined => { |
185 | 160 | const authHeader = c.req.header("Authorization") |
186 | 161 | if (!authHeader?.startsWith("Bearer ")) { |
@@ -216,77 +191,31 @@ const authenticateWithApiKey = ( |
216 | 191 | } |
217 | 192 |
|
218 | 193 | /** |
219 | | - * Authenticate via JWT bearer token. |
220 | | - */ |
221 | | -const authenticateWithJwt = (c: Context, token: string): Effect.Effect<AuthContext | null, never> => { |
222 | | - return Effect.gen(function* () { |
223 | | - const auth = getBetterAuth() |
224 | | - const headers = new Headers(c.req.raw.headers) |
225 | | - headers.set("authorization", `Bearer ${token}`) |
226 | | - |
227 | | - const session = yield* Effect.tryPromise({ |
228 | | - try: () => auth.api.getSession({ headers }), |
229 | | - catch: () => null, |
230 | | - }).pipe(Effect.orDie) |
231 | | - |
232 | | - if (!session?.user) { |
233 | | - return null |
234 | | - } |
235 | | - |
236 | | - const orgId = c.req.param("organizationId") |
237 | | - if (!orgId) { |
238 | | - return null |
239 | | - } |
240 | | - |
241 | | - const isMember = yield* validateOrganizationMembership(c, session.user.id, orgId) |
242 | | - if (!isMember) { |
243 | | - return null |
244 | | - } |
245 | | - |
246 | | - const authContext: AuthContext = { |
247 | | - userId: UserId(session.user.id), |
248 | | - organizationId: OrganizationId(orgId), |
249 | | - method: "jwt", |
250 | | - } |
251 | | - |
252 | | - return authContext |
253 | | - }).pipe(Effect.orDie) |
254 | | -} |
255 | | - |
256 | | -/** |
257 | | - * Main authentication effect that tries all authentication methods. |
| 194 | + * Authenticate via API key from the Authorization: Bearer header. |
258 | 195 | */ |
259 | 196 | const authenticate = (c: Context, options?: AuthMiddlewareOptions): Effect.Effect<AuthContext, UnauthorizedError> => { |
260 | 197 | return Effect.gen(function* () { |
261 | | - const apiKeyToken = extractApiKeyToken(c) |
262 | 198 | const bearerToken = extractBearerToken(c) |
263 | 199 |
|
264 | | - let authContext: AuthContext | null = null |
265 | | - |
266 | | - if (apiKeyToken) { |
267 | | - authContext = yield* authenticateWithApiKey(c, apiKeyToken, options) |
268 | | - } else if (bearerToken) { |
269 | | - authContext = yield* authenticateWithJwt(c, bearerToken) |
270 | | - } |
271 | | - |
272 | | - if (!authContext) { |
| 200 | + if (!bearerToken) { |
273 | 201 | return yield* new UnauthorizedError({ |
274 | 202 | message: "Authentication required", |
275 | 203 | }) |
276 | 204 | } |
277 | 205 |
|
278 | | - return authContext |
| 206 | + const authContext = yield* authenticateWithApiKey(c, bearerToken, options) |
| 207 | + if (authContext) return authContext |
| 208 | + |
| 209 | + return yield* new UnauthorizedError({ |
| 210 | + message: "Invalid API key", |
| 211 | + }) |
279 | 212 | }) |
280 | 213 | } |
281 | 214 |
|
282 | 215 | /** |
283 | 216 | * Create authentication middleware. |
284 | 217 | * |
285 | | - * This middleware validates requests using one of two methods: |
286 | | - * 1. JWT Bearer token (Better Auth) |
287 | | - * 2. API Key |
288 | | - * |
289 | | - * The middleware sets auth context on the Hono context for downstream handlers. |
| 218 | + * Validates API keys sent via the Authorization: Bearer header. |
290 | 219 | * Public routes should be excluded from this middleware. |
291 | 220 | */ |
292 | 221 | interface AuthMiddlewareOptions { |
|
0 commit comments