Skip to content

Commit 3ac2dad

Browse files
committed
chore: added-session-middleware-untested
1 parent f28ddfd commit 3ac2dad

11 files changed

Lines changed: 168 additions & 60 deletions

File tree

e2e/mock-api-v2/eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export default [
1010
rules: {
1111
'@typescript-eslint/no-empty-interface': 'off',
1212
'@typescript-eslint/no-empty-object-type': 'off',
13+
'require-yield': 'off',
1314
},
1415
},
1516
{

e2e/mock-api-v2/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@
77
"main": "./src/main.js",
88
"scripts": {
99
"build": "pnpm nx nxBuild",
10+
"dev": "pnpm nx build --watch & node dist/src/main.js --watch-path=./",
1011
"lint": "pnpm nx nxLint",
1112
"serve": "node dist/src/main.js",
12-
"serve:dev": "nodemon dist/src/main.js",
1313
"test": "pnpm nx nxTest"
1414
},
1515
"dependencies": {
1616
"@effect/language-service": "catalog:effect",
1717
"@effect/platform": "catalog:effect",
1818
"@effect/platform-node": "catalog:effect",
19-
"effect": "catalog:effect"
19+
"effect": "catalog:effect",
20+
"nanoid": "5.1.5"
2021
},
2122
"devDependencies": {
2223
"@effect/vitest": "catalog:effect"

e2e/mock-api-v2/src/helpers/test/match.test.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

e2e/mock-api-v2/src/main.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { TokensHandler } from './handlers/token.handler.js';
1919
import { UserInfoMockHandler } from './handlers/userinfo.handler.js';
2020
import { UserInfoMockService } from './services/userinfo.service.js';
2121
import { AuthorizationMock } from './middleware/Authorization.js';
22+
import { SessionMiddlewareMock } from './middleware/Session.js';
23+
import { SessionStorage } from './services/session.service.js';
2224

2325
const APIMock = HttpApiBuilder.api(MockApi).pipe(
2426
Layer.provide(HealthCheckLive),
@@ -31,11 +33,13 @@ const APIMock = HttpApiBuilder.api(MockApi).pipe(
3133
const ServerMock = HttpApiBuilder.serve(HttpMiddleware.logger).pipe(
3234
Layer.provide(HttpApiSwagger.layer()),
3335
Layer.provide(APIMock),
34-
Layer.provide(AuthorizeMock),
3536
Layer.provide(TokensMock),
36-
Layer.provide(UserInfoMockService),
3737
Layer.provide(IncrementStepIndexMock),
3838
Layer.provide(AuthorizationMock),
39+
Layer.provide(UserInfoMockService),
40+
Layer.provide(SessionMiddlewareMock),
41+
Layer.provide(SessionStorage.Default),
42+
Layer.provide(AuthorizeMock),
3943
Layer.provide(
4044
HttpApiBuilder.middlewareCors({
4145
allowedMethods: ['GET', 'PUT', 'POST', 'OPTIONS'],

e2e/mock-api-v2/src/middleware/Authorization.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Authorization extends HttpApiMiddleware.Tag<Authorization>()('Authorizatio
1717
),
1818
},
1919
}) {}
20+
2021
const AuthorizationMock = Layer.effect(
2122
Authorization,
2223
Effect.gen(function* () {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { HttpApiError, HttpApiMiddleware, HttpServerRequest } from '@effect/platform';
2+
import { SessionData, SessionStorage } from '../services/session.service.js';
3+
import { Context, Effect, Layer } from 'effect';
4+
5+
class Session extends Context.Tag('Session')<Session, SessionData>() {}
6+
7+
export class SessionMiddleware extends HttpApiMiddleware.Tag<SessionMiddleware>()('Session', {
8+
failure: HttpApiError.Unauthorized,
9+
provides: Session,
10+
}) {}
11+
12+
export const SessionMiddlewareMock = Layer.effect(
13+
SessionMiddleware,
14+
Effect.gen(function* () {
15+
const sessionStorage = yield* SessionStorage;
16+
17+
return Effect.gen(function* () {
18+
const request = yield* HttpServerRequest.HttpServerRequest;
19+
const sessionData = yield* sessionStorage
20+
.getSession(request.cookies.sessionId)
21+
.pipe(Effect.orDie);
22+
if (!sessionData) {
23+
const session = yield* sessionStorage.createSession({
24+
userId: request.cookies.userId,
25+
createdAt: new Date(),
26+
expiresAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour
27+
data: {},
28+
});
29+
30+
return session;
31+
}
32+
33+
yield* sessionStorage
34+
.refreshSession(request.cookies.sessionId, sessionData.expiresAt)
35+
.pipe(Effect.orDie);
36+
37+
return sessionData;
38+
});
39+
}),
40+
);

e2e/mock-api-v2/src/schemas/authorize.schema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ const _DavinciAuthorizeQuery = Schema.Struct({
2626
code: Schema.String,
2727
code_challenge: Schema.String,
2828
code_challenge_method: Schema.String,
29-
acr_values: Schema.String, // this should be optional
29+
acr_values: Schema.optional(Schema.String), // this should be optional
3030
});
31+
3132
interface DavinciAuthorizeQuery extends Schema.Schema.Type<typeof _DavinciAuthorizeQuery> {}
3233
const DavinciAuthorizeQuery: Schema.Schema<DavinciAuthorizeQuery, DavinciAuthorizeQuery> =
3334
_DavinciAuthorizeQuery;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { Effect } from 'effect';
2+
import { nanoid } from 'nanoid';
3+
4+
export type SessionData = {
5+
userId: string;
6+
createdAt: Date;
7+
expiresAt: Date;
8+
data?: Record<string, unknown>;
9+
};
10+
11+
export interface SessionStorageApi {
12+
createSession: (data: SessionData) => Effect.Effect<string, Error, never>;
13+
getSession: (sessionId: string) => Effect.Effect<SessionData | null, Error, never>;
14+
deleteSession: (sessionId: string) => Effect.Effect<void, Error, never>;
15+
updateSession: (sessionId: string, data: SessionData) => Effect.Effect<void, Error, never>;
16+
refreshSession: (sessionId: string, expiryDate?: Date) => Effect.Effect<void, Error, never>;
17+
isSessionExpired: (sessionData: SessionData) => boolean;
18+
cleanupExpiredSessions: () => Effect.Effect<void, never, never>;
19+
}
20+
21+
export class SessionStorage extends Effect.Service<SessionStorage>()('SessionStorage', {
22+
sync: () => {
23+
// In-memory session store
24+
const _store = new Map<string, SessionData>();
25+
26+
// Check if a session is expired
27+
const isSessionExpired = (sessionData: SessionData): boolean => {
28+
const now = new Date();
29+
return sessionData.expiresAt < now;
30+
};
31+
32+
return {
33+
createSession: (data: SessionData) => {
34+
const sessionId = nanoid();
35+
_store.set(sessionId, data);
36+
return Effect.succeed(data);
37+
},
38+
39+
getSession: Effect.fn(function* (sessionId: string) {
40+
const session = _store.get(sessionId);
41+
42+
if (!session) {
43+
return null;
44+
}
45+
46+
// Check if session is expired
47+
if (isSessionExpired(session)) {
48+
_store.delete(sessionId);
49+
return null;
50+
}
51+
52+
return session;
53+
}),
54+
55+
deleteSession: Effect.fn(function* (sessionId: string) {
56+
_store.delete(sessionId);
57+
return undefined;
58+
}),
59+
60+
updateSession: Effect.fn(function* (sessionId: string, data: SessionData) {
61+
if (!_store.has(sessionId)) {
62+
return new Error('Session not found');
63+
}
64+
_store.set(sessionId, data);
65+
return data;
66+
}),
67+
68+
refreshSession: Effect.fn(function* (sessionId: string, expiryDate?: Date) {
69+
const session = _store.get(sessionId);
70+
71+
if (!session) {
72+
return Effect.fail(new Error('Session not found'));
73+
}
74+
75+
if (isSessionExpired(session)) {
76+
_store.delete(sessionId);
77+
return new Error('Session has expired');
78+
}
79+
80+
// Update expiry date
81+
const newExpiryDate = expiryDate || new Date(Date.now() + 24 * 60 * 60 * 1000); // Default: 24 hours from now
82+
session.expiresAt = newExpiryDate;
83+
_store.set(sessionId, session);
84+
85+
return session.data;
86+
}),
87+
88+
isSessionExpired,
89+
90+
cleanupExpiredSessions: Effect.fn(function* () {
91+
for (const [sessionId, session] of _store.entries()) {
92+
if (isSessionExpired(session)) {
93+
_store.delete(sessionId);
94+
}
95+
}
96+
return undefined;
97+
}),
98+
};
99+
},
100+
}) {}

e2e/mock-api-v2/src/spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { PingOneCustomHtmlResponseBody } from './schemas/custom-html-template/cu
1616
import { TokenResponseBody } from './schemas/token/token.schema.js';
1717
import { UserInfoSchema } from './schemas/userinfo/userinfo.schema.js';
1818
import { Authorization } from './middleware/Authorization.js';
19+
import { SessionMiddleware } from './middleware/Session.js';
1920

2021
const MockApi = HttpApi.make('MyApi')
2122
.add(
@@ -52,6 +53,7 @@ const MockApi = HttpApi.make('MyApi')
5253
.addError(HttpApiError.Unauthorized)
5354
.setPath(Schema.Struct({ envid: Schema.String })),
5455
)
56+
.middleware(SessionMiddleware)
5557
.annotate(OpenApi.Description, 'Acquire an Access Token from Davinci/P1'),
5658
)
5759
.add(

e2e/mock-api-v2/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default defineConfig({
1414
globals: false,
1515
watch: false,
1616
environment: 'jsdom',
17+
passWithNoTests: true,
1718
pool: 'forks',
1819
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
1920
reporters: ['default', 'json', 'html'],

0 commit comments

Comments
 (0)