Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(server): providing layers #2

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/server/src/api/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const ApiDemoLive = HttpApiBuilder.group(api, 'demo', handlers =>
return new ResponseProtected({ userId: user.id })
}),
),
).pipe(
Layer.provide(AuthMiddlewareLive)
)

const ApiUsersLive = HttpApiBuilder.group(api, 'users', handlers =>
Expand All @@ -102,8 +104,10 @@ const ApiUsersLive = HttpApiBuilder.group(api, 'users', handlers =>
})
}),
),
).pipe(
Layer.provide(AuthMiddlewareLive)
)

export const ApiLive = HttpApiBuilder.api(api).pipe(
Layer.provide([ApiDemoLive, ApiUsersLive, AuthMiddlewareLive]),
Layer.provide([ApiDemoLive, ApiUsersLive]),
)
73 changes: 35 additions & 38 deletions apps/server/src/api/auth_middleware.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { HttpApiMiddleware } from '@effect/platform'
import * as ServerRequest from '@effect/platform/HttpServerRequest'
import { HttpApiMiddleware, HttpServerRequest } from '@effect/platform'
import * as ServerResponse from '@effect/platform/HttpServerResponse'
import { Effect, Layer, Schema } from 'effect'
import {
Expand All @@ -9,7 +8,7 @@ import {
} from 'supertokens-node/framework/custom'
import SupertokensSession from 'supertokens-node/recipe/session'

import { Forbidden, Unauthorized } from './types.js'
import { CurrentUser, Forbidden, Unauthorized } from './types.js'

type NextFunction = (err?: any) => void

Expand All @@ -28,7 +27,7 @@ export class AuthMiddleware extends HttpApiMiddleware.Tag<AuthMiddleware>()(
export const AuthMiddlewareLive = Layer.effect(
AuthMiddleware,
Effect.gen(function* () {
const request = yield* ServerRequest.HttpServerRequest
const request = yield* HttpServerRequest.HttpServerRequest

// Create PreParsedRequest for Supertokens
const preParsedRequest = new PreParsedRequest({
Expand Down Expand Up @@ -57,7 +56,7 @@ export const AuthMiddlewareLive = Layer.effect(
)

if (error) {
throw error
return yield* new MiddlewareError()
}

// handled is valid for these cases:
Expand All @@ -80,42 +79,40 @@ export const AuthMiddlewareLive = Layer.effect(
// 1. Regular application endpoints
// 2. API routes that need session verification
// 3. Any non-auth related requests
try {
const session = yield* Effect.promise(() =>
SupertokensSession.getSession(preParsedRequest, baseResponse, {
sessionRequired: false,
}),
)

// if (session) {
// const userId = session?.getUserId()
// // return yield* app.pipe(
// // Effect.provideService(CurrentUser, { id: userId })
// // );
// }
const session: SupertokensSession.SessionContainer | undefined =
yield* Effect.tryPromise({
try: () =>
SupertokensSession.getSession(preParsedRequest, baseResponse, {
sessionRequired: false,
}),
catch: err => {
// Docs: https://github.com/supertokens/supertokens-node/blob/7e33a06f4283a150600a5eabda31615fc5827023/lib/ts/recipe/session/error.ts
if (SupertokensSession.Error.isErrorFromSuperTokens(err)) {
switch (err.type) {
case SupertokensSession.Error.TRY_REFRESH_TOKEN:
case SupertokensSession.Error.UNAUTHORISED:
return new Unauthorized()
case SupertokensSession.Error.INVALID_CLAIMS:
return new Forbidden()
case SupertokensSession.Error.TOKEN_THEFT_DETECTED:
// You might want to handle this case differently, perhaps logging the incident
return new Unauthorized()
case SupertokensSession.Error.CLEAR_DUPLICATE_SESSION_COOKIES:
// Handle duplicate session cookies - usually means clearing them
return new Unauthorized()
default:
throw err
}
}
},
})

if (!session) {
return yield* new Unauthorized()
} catch (err) {
// Docs: https://github.com/supertokens/supertokens-node/blob/7e33a06f4283a150600a5eabda31615fc5827023/lib/ts/recipe/session/error.ts
if (SupertokensSession.Error.isErrorFromSuperTokens(err)) {
switch (err.type) {
case SupertokensSession.Error.TRY_REFRESH_TOKEN:
case SupertokensSession.Error.UNAUTHORISED:
return yield* new Unauthorized()
case SupertokensSession.Error.INVALID_CLAIMS:
return yield* new Forbidden()
case SupertokensSession.Error.TOKEN_THEFT_DETECTED:
// You might want to handle this case differently, perhaps logging the incident
return yield* new Unauthorized()
case SupertokensSession.Error.CLEAR_DUPLICATE_SESSION_COOKIES:
// Handle duplicate session cookies - usually means clearing them
return yield* new Unauthorized()
default:
throw err
}
}
throw err
}

const userId = session.getUserId()
return yield* new CurrentUser(userId)
}),
)

Expand Down
14 changes: 5 additions & 9 deletions apps/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createServer } from 'node:http'
import {
HttpApiBuilder,
HttpApiSwagger,
HttpMiddleware,
HttpServer,
} from '@effect/platform'
import { NodeHttpServer, NodeRuntime } from '@effect/platform-node'
import { Config, Layer } from 'effect'
import { Layer } from 'effect'
import { createServer } from 'node:http'

import { ApiLive } from './api/apis.js'
import { MiddlewareCorsLive } from './api/cors.js'
Expand All @@ -25,14 +25,10 @@ const ServerLive = NodeHttpServer.layer(() => createServer(), {
// Register API with HTTP server
const HttpLive = HttpApiBuilder.serve(HttpMiddleware.logger)
.pipe(
Layer.provide(MiddlewareCorsLive),
Layer.provide(SupertokensService.layer),
Layer.provide(ApiLive),
Layer.provide(ServerLive),
HttpServer.withLogAddress, // Log server address
Layer.provide(HttpApiSwagger.layer({ path: '/docs' })),
)
.pipe(Layer.provide(ServerLive))
Layer.provide([MiddlewareCorsLive, SupertokensService.layer, ApiLive, ServerLive, HttpApiSwagger.layer({ path: '/docs' }).pipe(
Layer.provide(ApiLive)
)]))

// Run server
NodeRuntime.runMain(Layer.launch(HttpLive))
6 changes: 5 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
"vite-plugin-pwa": "^0.21.1"
},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"yarn": ">=4.1.0"
},
"type": "module",
"workspaces": ["apps/*"],
"workspaces": [
"apps/*"
],
"scripts": {
"lint": "yarn lint:syncpack && yarn lint:biome",
"lint:biome": "biome check --write --no-errors-on-unmatched",
Expand Down