ocean-brain 0.7.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +3 -3
- package/dist/mcp.js +180 -736
- package/package.json +4 -1
- package/server/client/dist/assets/ArrowRight.es-C4zlc54Z.js +1 -0
- package/server/client/dist/assets/{Calendar-Bz7jheg6.js → Calendar-COrN6Hjv.js} +2 -2
- package/server/client/dist/assets/Callout-BK0K9E74.js +1 -0
- package/server/client/dist/assets/Copy.es-zm8f_XXX.js +1 -0
- package/server/client/dist/assets/Graph-BH1fzhue.js +1 -0
- package/server/client/dist/assets/{Image-DxWhlIDG.js → Image-KXuHOG4b.js} +1 -1
- package/server/client/dist/assets/Image.es-BKahRbIm.js +1 -0
- package/server/client/dist/assets/Note-C143cKKw.js +12 -0
- package/server/client/dist/assets/Plus.es-BqeC8T8h.js +1 -0
- package/server/client/dist/assets/Reminders-BYG5rz1M.js +1 -0
- package/server/client/dist/assets/Search-DBWsIMDP.js +1 -0
- package/server/client/dist/assets/{SurfaceCard-CfL1jD-O.js → SurfaceCard-CNSQfYfM.js} +1 -1
- package/server/client/dist/assets/Tag-5aCfSO8Y.js +1 -0
- package/server/client/dist/assets/TagNotes-BPZjN-6l.js +1 -0
- package/server/client/dist/assets/Trash.es-CRQkTnXZ.js +1 -0
- package/server/client/dist/assets/ViewNotes-DGLNF96x.js +1 -0
- package/server/client/dist/assets/ViewSectionDialog-CzskgmMF.js +102 -0
- package/server/client/dist/assets/Views-DR5jT23G.js +1 -0
- package/server/client/dist/assets/{graph-vendor-CUxe67Lr.js → graph-vendor-UTvF2NS_.js} +2 -2
- package/server/client/dist/assets/image.api-8dlMwzMd.js +17 -0
- package/server/client/dist/assets/{index-C5BGzDgt.css → index-BEUqDuAZ.css} +1 -1
- package/server/client/dist/assets/{index-DiMt3Dz4.js → index-DGIAh3ZY.js} +1 -1
- package/server/client/dist/assets/index-VHp17GQX.js +342 -0
- package/server/client/dist/assets/manage-image-RqtWKas2.js +1 -0
- package/server/client/dist/assets/{manage-image-detail-Bqw8NURO.js → manage-image-detail-BTSIbLlQ.js} +1 -1
- package/server/client/dist/assets/mcp-EDurbggR.js +4 -0
- package/server/client/dist/assets/mcp-admin-adapter-DmGUo5G8.js +1 -0
- package/server/client/dist/assets/{note-core-Z6kqulGB.js → note-core-BklwyZHk.js} +1 -1
- package/server/client/dist/assets/{note-vendor-i1M2FSI2.js → note-vendor-DkYKUmLo.js} +1 -1
- package/server/client/dist/assets/{placeholder-Ro00byT7.js → placeholder-DeKgSBnC.js} +4 -4
- package/server/client/dist/assets/{properties-Tid6-ghT.js → properties-BjrZvOYn.js} +1 -1
- package/server/client/dist/assets/tag.api-Dl8DQd0m.js +21 -0
- package/server/client/dist/assets/trash-C5ntddLu.js +1 -0
- package/server/client/dist/assets/{useReminderMutate-BWymIujb.js → useReminderMutate-CoXXqJY2.js} +1 -1
- package/server/client/dist/index.html +5 -5
- package/server/dist/app.js +2 -2
- package/server/dist/app.js.map +1 -1
- package/server/dist/features/auth/http/api.js +5 -1
- package/server/dist/features/auth/http/api.js.map +1 -1
- package/server/dist/features/auth/http/login-page.js +1 -1
- package/server/dist/features/auth/http/pages.js +8 -1
- package/server/dist/features/auth/http/pages.js.map +1 -1
- package/server/dist/features/auth/service.js +16 -7
- package/server/dist/features/auth/service.js.map +1 -1
- package/server/dist/features/image/http/upload.js +34 -23
- package/server/dist/features/image/http/upload.js.map +1 -1
- package/server/dist/features/image/services/validation.js +111 -0
- package/server/dist/features/image/services/validation.js.map +1 -0
- package/server/dist/features/mcp-admin/http/handlers.js +11 -3
- package/server/dist/features/mcp-admin/http/handlers.js.map +1 -1
- package/server/dist/features/note/http/mcp.js +8 -43
- package/server/dist/features/note/http/mcp.js.map +1 -1
- package/server/dist/features/note/services/cleanup.js +2 -2
- package/server/dist/features/note/services/cleanup.js.map +1 -1
- package/server/dist/features/note/services/markdown-intent-write.js +11 -43
- package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
- package/server/dist/features/note/services/markdown-patch.js +4 -123
- package/server/dist/features/note/services/markdown-patch.js.map +1 -1
- package/server/dist/features/note/services/write.js +13 -13
- package/server/dist/features/note/services/write.js.map +1 -1
- package/server/dist/features/tag/graphql/tag.query.resolver.js +16 -1
- package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -1
- package/server/dist/features/tag/graphql/tag.type-defs.js +2 -0
- package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -1
- package/server/dist/modules/app-version.js +105 -0
- package/server/dist/modules/app-version.js.map +1 -0
- package/server/dist/modules/auth-guard.js +21 -2
- package/server/dist/modules/auth-guard.js.map +1 -1
- package/server/dist/modules/auth-redirect.js +10 -0
- package/server/dist/modules/auth-redirect.js.map +1 -0
- package/server/dist/modules/mcp-auth.js +62 -1
- package/server/dist/modules/mcp-auth.js.map +1 -1
- package/server/dist/modules/rate-limit.js +15 -1
- package/server/dist/modules/rate-limit.js.map +1 -1
- package/server/dist/modules/server-events-handler.js +17 -0
- package/server/dist/modules/server-events-handler.js.map +1 -1
- package/server/dist/modules/session-store.js +149 -0
- package/server/dist/modules/session-store.js.map +1 -0
- package/server/dist/paths.js +1 -0
- package/server/dist/paths.js.map +1 -1
- package/server/dist/routes/api.js +39 -20
- package/server/dist/routes/api.js.map +1 -1
- package/server/dist/routes/auth-pages.js +13 -2
- package/server/dist/routes/auth-pages.js.map +1 -1
- package/server/dist/routes/client.js +63 -3
- package/server/dist/routes/client.js.map +1 -1
- package/server/dist/routes/graphql.js +23 -19
- package/server/dist/routes/graphql.js.map +1 -1
- package/server/client/dist/assets/Callout-DlFLkTCG.js +0 -1
- package/server/client/dist/assets/Graph-BdxtJvog.js +0 -1
- package/server/client/dist/assets/Image.es-BFodqKS5.js +0 -1
- package/server/client/dist/assets/Note-BgMxsWZT.js +0 -22
- package/server/client/dist/assets/Plus.es-Yq7J1Ur4.js +0 -1
- package/server/client/dist/assets/Reminders-CUsR56ff.js +0 -1
- package/server/client/dist/assets/Search-DncrGgOZ.js +0 -1
- package/server/client/dist/assets/Tag-CKDLs2Y6.js +0 -1
- package/server/client/dist/assets/TagNotes-ByX_Qias.js +0 -1
- package/server/client/dist/assets/Trash.es-BPrF6TPV.js +0 -1
- package/server/client/dist/assets/ViewNotes-r1kxKZ8O.js +0 -1
- package/server/client/dist/assets/ViewSectionTableRenderer-DQ4UeCnm.js +0 -102
- package/server/client/dist/assets/Views-FzFBtNT2.js +0 -1
- package/server/client/dist/assets/image.api-Bcg3TFCG.js +0 -17
- package/server/client/dist/assets/index-BFfQKAUY.js +0 -361
- package/server/client/dist/assets/manage-image-DzDlncCM.js +0 -1
- package/server/client/dist/assets/mcp-BqasqTWC.js +0 -1
- package/server/client/dist/assets/trash-bHK02-Up.js +0 -1
- package/server/dist/features/image/services/remote-fetch.js +0 -176
- package/server/dist/features/image/services/remote-fetch.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/note/services/write.ts"],"sourcesContent":["import models, { type NoteLayout, Prisma } from '~/models.js';\nimport { buildNoteSearchProjection } from './search.js';\nimport { captureNoteBaseline } from './snapshot.js';\nimport { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from './write-conflict.js';\n\nexport interface NoteWriteRecord {\n id: number;\n title: string;\n content: string;\n layout: NoteLayout;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n}\n\ntype NoteWriteData = {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: number[];\n};\n\ninterface UpdateNoteWithVersionGuardInput {\n id: number;\n data: NoteWriteData;\n editSessionId?: string;\n expectedUpdatedAt?: string;\n snapshotMeta?: string;\n force?: boolean;\n}\n\nexport interface GuardedNoteWriteResult {\n note: NoteWriteRecord;\n snapshot: unknown;\n}\n\ninterface NoteWriteDeps {\n findNoteForWrite: (\n id: number,\n ) => Promise<Pick<NoteWriteRecord, 'title' | 'content' | 'updatedAt' | 'pinned' | 'order' | 'layout'> | null>;\n findNoteVersion: (id: number) => Promise<Pick<NoteWriteRecord, 'updatedAt'> | null>;\n captureBaseline: (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline: Pick<NoteWriteRecord, 'id' | 'title' | 'content' | 'pinned' | 'order' | 'layout'>;\n force?: boolean;\n }) => Promise<unknown>;\n updateNote: (input: {\n where: {\n id: number;\n updatedAt?: Date;\n };\n data: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n searchableText: string;\n searchableTextVersion: number;\n tags?: {\n set: Array<{ id: number }>;\n };\n };\n }) => Promise<NoteWriteRecord>;\n isRecordNotFoundError: (error: unknown) => boolean;\n}\n\nconst isRecordNotFoundError = (error: unknown) => {\n return error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025';\n};\n\nexport const createNoteWriteService = (deps: NoteWriteDeps) => {\n const updateNoteWithVersionGuardAndSnapshot = async ({\n id,\n data,\n editSessionId,\n expectedUpdatedAt,\n snapshotMeta,\n force = false,\n }: UpdateNoteWithVersionGuardInput): Promise<GuardedNoteWriteResult | null> => {\n const existingNote = await deps.findNoteForWrite(id);\n\n if (!existingNote) {\n return null;\n }\n\n const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);\n\n if (expectedTimestamp === null && !force) {\n throw new MissingNoteVersionError();\n }\n\n const nextTitle = data.title ?? existingNote.title;\n const nextContent = data.content ?? existingNote.content;\n const where = force\n ? { id }\n : { id, updatedAt: new Date(expectedTimestamp ?? existingNote.updatedAt.getTime()) };\n\n try {\n const updatedNote = await deps.updateNote({\n where,\n data: {\n ...(data.title !== undefined ? { title: data.title } : {}),\n ...(data.content !== undefined ? { content: data.content } : {}),\n ...(data.layout !== undefined ? { layout: data.layout } : {}),\n ...buildNoteSearchProjection({\n title: nextTitle,\n content: nextContent,\n }),\n ...(data.tagIds ? { tags: { set: data.tagIds.map((tagId) => ({ id: tagId })) } } : {}),\n },\n });\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/write.ts"],"sourcesContent":["import models, { type NoteLayout, Prisma } from '~/models.js';\nimport { buildNoteSearchProjection } from './search.js';\nimport { captureNoteBaseline } from './snapshot.js';\nimport { createNoteVersionConflictError, MissingNoteVersionError, parseNoteVersion } from './write-conflict.js';\n\nexport interface NoteWriteRecord {\n id: number;\n title: string;\n content: string;\n layout: NoteLayout;\n createdAt: Date;\n updatedAt: Date;\n pinned: boolean;\n order: number;\n}\n\ntype NoteWriteData = {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n tagIds?: number[];\n};\n\ninterface UpdateNoteWithVersionGuardInput {\n id: number;\n data: NoteWriteData;\n editSessionId?: string;\n expectedUpdatedAt?: string;\n snapshotMeta?: string;\n force?: boolean;\n}\n\nexport interface GuardedNoteWriteResult {\n note: NoteWriteRecord;\n snapshot: unknown;\n}\n\ninterface NoteWriteDeps {\n findNoteForWrite: (\n id: number,\n ) => Promise<Pick<NoteWriteRecord, 'title' | 'content' | 'updatedAt' | 'pinned' | 'order' | 'layout'> | null>;\n findNoteVersion: (id: number) => Promise<Pick<NoteWriteRecord, 'updatedAt'> | null>;\n captureBaseline: (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline: Pick<NoteWriteRecord, 'id' | 'title' | 'content' | 'pinned' | 'order' | 'layout'>;\n force?: boolean;\n }) => Promise<unknown>;\n updateNote: (input: {\n where: {\n id: number;\n updatedAt?: Date;\n };\n data: {\n title?: string;\n content?: string;\n layout?: NoteLayout;\n searchableText: string;\n searchableTextVersion: number;\n tags?: {\n set: Array<{ id: number }>;\n };\n };\n }) => Promise<NoteWriteRecord>;\n isRecordNotFoundError: (error: unknown) => boolean;\n}\n\nconst isRecordNotFoundError = (error: unknown) => {\n return error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025';\n};\n\nexport const createNoteWriteService = (deps: NoteWriteDeps) => {\n const updateNoteWithVersionGuardAndSnapshot = async ({\n id,\n data,\n editSessionId,\n expectedUpdatedAt,\n snapshotMeta,\n force = false,\n }: UpdateNoteWithVersionGuardInput): Promise<GuardedNoteWriteResult | null> => {\n const existingNote = await deps.findNoteForWrite(id);\n\n if (!existingNote) {\n return null;\n }\n\n const expectedTimestamp = parseNoteVersion(expectedUpdatedAt);\n\n if (expectedTimestamp === null && !force) {\n throw new MissingNoteVersionError();\n }\n\n const nextTitle = data.title ?? existingNote.title;\n const nextContent = data.content ?? existingNote.content;\n const where = force\n ? { id }\n : { id, updatedAt: new Date(expectedTimestamp ?? existingNote.updatedAt.getTime()) };\n\n try {\n const snapshot = await deps.captureBaseline({\n noteId: id,\n baseline: {\n id,\n title: existingNote.title,\n content: existingNote.content,\n pinned: existingNote.pinned,\n order: existingNote.order,\n layout: existingNote.layout,\n },\n ...(editSessionId && !force ? { editSessionId } : {}),\n ...(snapshotMeta ? { meta: snapshotMeta } : {}),\n ...(force ? { force: true } : {}),\n });\n\n const updatedNote = await deps.updateNote({\n where,\n data: {\n ...(data.title !== undefined ? { title: data.title } : {}),\n ...(data.content !== undefined ? { content: data.content } : {}),\n ...(data.layout !== undefined ? { layout: data.layout } : {}),\n ...buildNoteSearchProjection({\n title: nextTitle,\n content: nextContent,\n }),\n ...(data.tagIds ? { tags: { set: data.tagIds.map((tagId) => ({ id: tagId })) } } : {}),\n },\n });\n\n return {\n note: updatedNote,\n snapshot,\n };\n } catch (error) {\n if (deps.isRecordNotFoundError(error)) {\n const currentNote = await deps.findNoteVersion(id);\n\n if (!currentNote) {\n return null;\n }\n\n if (expectedTimestamp === null || currentNote.updatedAt.getTime() === expectedTimestamp) {\n throw error;\n }\n\n throw createNoteVersionConflictError({\n expectedUpdatedAt: expectedTimestamp,\n currentUpdatedAt: currentNote.updatedAt.getTime(),\n });\n }\n\n throw error;\n }\n };\n\n return {\n updateNoteWithVersionGuard: async (input: UpdateNoteWithVersionGuardInput): Promise<NoteWriteRecord | null> => {\n const result = await updateNoteWithVersionGuardAndSnapshot(input);\n return result?.note ?? null;\n },\n updateNoteWithVersionGuardAndSnapshot,\n };\n};\n\nconst defaultNoteWriteService = createNoteWriteService({\n findNoteForWrite: (id) =>\n models.note.findUnique({\n where: { id },\n select: {\n title: true,\n content: true,\n updatedAt: true,\n pinned: true,\n order: true,\n layout: true,\n },\n }),\n findNoteVersion: (id) =>\n models.note.findUnique({\n where: { id },\n select: {\n updatedAt: true,\n },\n }),\n captureBaseline: captureNoteBaseline,\n updateNote: ({ where, data }) =>\n models.note.update({\n where,\n data,\n }),\n isRecordNotFoundError,\n});\n\nexport const updateNoteWithVersionGuard = async (input: UpdateNoteWithVersionGuardInput) => {\n return defaultNoteWriteService.updateNoteWithVersionGuard(input);\n};\n\nexport const updateNoteWithVersionGuardAndSnapshot = async (input: UpdateNoteWithVersionGuardInput) => {\n return defaultNoteWriteService.updateNoteWithVersionGuardAndSnapshot(input);\n};\n"],"mappings":"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","names":["updateNoteWithVersionGuardAndSnapshot"]}
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import models from "../../../models.js";
|
|
2
|
+
const TAG_SORT_FIELDS = /* @__PURE__ */ new Set(["referenceCount", "name"]);
|
|
3
|
+
const normalizeSortBy = (sortBy) => {
|
|
4
|
+
return TAG_SORT_FIELDS.has(sortBy) ? sortBy : "referenceCount";
|
|
5
|
+
};
|
|
6
|
+
const normalizeSortOrder = (sortOrder) => {
|
|
7
|
+
return sortOrder === "asc" ? "asc" : "desc";
|
|
8
|
+
};
|
|
9
|
+
const getTagOrderBy = (sortBy, sortOrder) => {
|
|
10
|
+
if (sortBy === "referenceCount") {
|
|
11
|
+
return { notes: { _count: sortOrder } };
|
|
12
|
+
}
|
|
13
|
+
return { [sortBy]: sortOrder };
|
|
14
|
+
};
|
|
2
15
|
const tagQueryResolvers = {
|
|
3
16
|
allTags: async (_, {
|
|
4
17
|
searchFilter,
|
|
@@ -8,11 +21,13 @@ const tagQueryResolvers = {
|
|
|
8
21
|
name: { contains: searchFilter.query },
|
|
9
22
|
NOT: { notes: { none: {} } }
|
|
10
23
|
};
|
|
24
|
+
const sortBy = normalizeSortBy(searchFilter.sortBy);
|
|
25
|
+
const sortOrder = normalizeSortOrder(searchFilter.sortOrder);
|
|
11
26
|
const tags = models.tag.findMany({
|
|
12
27
|
skip: pagination.offset,
|
|
13
28
|
take: pagination.limit,
|
|
14
29
|
where,
|
|
15
|
-
orderBy:
|
|
30
|
+
orderBy: getTagOrderBy(sortBy, sortOrder)
|
|
16
31
|
});
|
|
17
32
|
return {
|
|
18
33
|
totalCount: await models.tag.count({ where }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/tag/graphql/tag.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport type { Prisma } from '~/models.js';\nimport models from '~/models.js';\nimport type { Pagination, SearchFilter } from '~/types/index.js';\n\ntype TagQueryResolvers = NonNullable<IResolvers['Query']>;\n\nexport const tagQueryResolvers: TagQueryResolvers = {\n allTags: async (\n _,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const where: Prisma.TagWhereInput = {\n name: { contains: searchFilter.query },\n NOT: { notes: { none: {} } },\n };\n const tags = models.tag.findMany({\n skip: pagination.offset,\n take: pagination.limit,\n where,\n orderBy:
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/tag/graphql/tag.query.resolver.ts"],"sourcesContent":["import type { IResolvers } from '@graphql-tools/utils';\nimport type { Prisma } from '~/models.js';\nimport models from '~/models.js';\nimport type { Pagination, SearchFilter } from '~/types/index.js';\n\ntype TagQueryResolvers = NonNullable<IResolvers['Query']>;\ntype TagSortBy = 'referenceCount' | 'name';\ntype TagSortOrder = 'asc' | 'desc';\n\nconst TAG_SORT_FIELDS = new Set<TagSortBy>(['referenceCount', 'name']);\n\nconst normalizeSortBy = (sortBy?: string): TagSortBy => {\n return TAG_SORT_FIELDS.has(sortBy as TagSortBy) ? (sortBy as TagSortBy) : 'referenceCount';\n};\n\nconst normalizeSortOrder = (sortOrder?: string): TagSortOrder => {\n return sortOrder === 'asc' ? 'asc' : 'desc';\n};\n\nconst getTagOrderBy = (sortBy: TagSortBy, sortOrder: TagSortOrder): Prisma.TagOrderByWithRelationInput => {\n if (sortBy === 'referenceCount') {\n return { notes: { _count: sortOrder } };\n }\n\n return { [sortBy]: sortOrder };\n};\n\nexport const tagQueryResolvers: TagQueryResolvers = {\n allTags: async (\n _,\n {\n searchFilter,\n pagination,\n }: {\n searchFilter: SearchFilter;\n pagination: Pagination;\n },\n ) => {\n const where: Prisma.TagWhereInput = {\n name: { contains: searchFilter.query },\n NOT: { notes: { none: {} } },\n };\n const sortBy = normalizeSortBy(searchFilter.sortBy);\n const sortOrder = normalizeSortOrder(searchFilter.sortOrder);\n const tags = models.tag.findMany({\n skip: pagination.offset,\n take: pagination.limit,\n where,\n orderBy: getTagOrderBy(sortBy, sortOrder),\n });\n\n return {\n totalCount: await models.tag.count({ where }),\n tags: await tags,\n };\n },\n};\n"],"mappings":"AAEA,OAAO,YAAY;AAOnB,MAAM,kBAAkB,oBAAI,IAAe,CAAC,kBAAkB,MAAM,CAAC;AAErE,MAAM,kBAAkB,CAAC,WAA+B;AACpD,SAAO,gBAAgB,IAAI,MAAmB,IAAK,SAAuB;AAC9E;AAEA,MAAM,qBAAqB,CAAC,cAAqC;AAC7D,SAAO,cAAc,QAAQ,QAAQ;AACzC;AAEA,MAAM,gBAAgB,CAAC,QAAmB,cAAgE;AACtG,MAAI,WAAW,kBAAkB;AAC7B,WAAO,EAAE,OAAO,EAAE,QAAQ,UAAU,EAAE;AAAA,EAC1C;AAEA,SAAO,EAAE,CAAC,MAAM,GAAG,UAAU;AACjC;AAEO,MAAM,oBAAuC;AAAA,EAChD,SAAS,OACL,GACA;AAAA,IACI;AAAA,IACA;AAAA,EACJ,MAIC;AACD,UAAM,QAA8B;AAAA,MAChC,MAAM,EAAE,UAAU,aAAa,MAAM;AAAA,MACrC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE;AAAA,IAC/B;AACA,UAAM,SAAS,gBAAgB,aAAa,MAAM;AAClD,UAAM,YAAY,mBAAmB,aAAa,SAAS;AAC3D,UAAM,OAAO,OAAO,IAAI,SAAS;AAAA,MAC7B,MAAM,WAAW;AAAA,MACjB,MAAM,WAAW;AAAA,MACjB;AAAA,MACA,SAAS,cAAc,QAAQ,SAAS;AAAA,IAC5C,CAAC;AAED,WAAO;AAAA,MACH,YAAY,MAAM,OAAO,IAAI,MAAM,EAAE,MAAM,CAAC;AAAA,MAC5C,MAAM,MAAM;AAAA,IAChB;AAAA,EACJ;AACJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/tag/graphql/tag.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const tagType = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n input SearchFilterInput {\n query: String!\n }\n\n type Tag {\n id: ID!\n name: String!\n createdAt: String!\n updatedAt: String!\n referenceCount: Int!\n }\n\n type Tags {\n totalCount: Int!\n tags: [Tag!]!\n }\n`;\n\nexport const tagQuery = gql`\n type Query {\n allTags(searchFilter: SearchFilterInput, pagination: PaginationInput): Tags!\n }\n`;\n\nexport const tagMutation = gql`\n type Mutation {\n createTag(name: String!): Tag!\n }\n`;\n\nexport const tagTypeDefs = `\n ${tagType}\n ${tagQuery}\n ${tagMutation}\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/tag/graphql/tag.type-defs.ts"],"sourcesContent":["import { gql } from '~/modules/graphql.js';\n\nexport const tagType = gql`\n input PaginationInput {\n limit: Int!\n offset: Int!\n }\n\n input SearchFilterInput {\n query: String!\n sortBy: String\n sortOrder: String\n }\n\n type Tag {\n id: ID!\n name: String!\n createdAt: String!\n updatedAt: String!\n referenceCount: Int!\n }\n\n type Tags {\n totalCount: Int!\n tags: [Tag!]!\n }\n`;\n\nexport const tagQuery = gql`\n type Query {\n allTags(searchFilter: SearchFilterInput, pagination: PaginationInput): Tags!\n }\n`;\n\nexport const tagMutation = gql`\n type Mutation {\n createTag(name: String!): Tag!\n }\n`;\n\nexport const tagTypeDefs = `\n ${tagType}\n ${tagQuery}\n ${tagMutation}\n`;\n"],"mappings":"AAAA,SAAS,WAAW;AAEb,MAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA0BhB,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAMjB,MAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAMpB,MAAM,cAAc;AAAA,MACrB,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW;AAAA;","names":[]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { paths } from "../paths.js";
|
|
4
|
+
const OCEAN_BRAIN_RELEASES_URL = "https://github.com/baealex/ocean-brain/releases";
|
|
5
|
+
const OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = "X-Ocean-Brain-MCP-Compatibility-Version";
|
|
6
|
+
const OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = "X-Ocean-Brain-MCP-Client-Version";
|
|
7
|
+
const normalizeVersion = (version) => version.trim().replace(/^v/i, "");
|
|
8
|
+
const parseMajorMinorVersion = (version) => {
|
|
9
|
+
const match = normalizeVersion(version).match(/^(\d+)\.(\d+)(?:\.\d+)?(?:[-+].*)?$/);
|
|
10
|
+
if (!match) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
major: Number(match[1]),
|
|
15
|
+
minor: Number(match[2])
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
const formatMcpVersionRequirement = (mcpCompatibilityVersion) => {
|
|
19
|
+
const parsed = parseMajorMinorVersion(mcpCompatibilityVersion);
|
|
20
|
+
return parsed ? `${parsed.major}.${parsed.minor}.x` : "unknown";
|
|
21
|
+
};
|
|
22
|
+
const readPackageMetadata = (packageJsonPath) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
25
|
+
} catch {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const readPackageVersion = (packageJsonPath) => {
|
|
30
|
+
const packageJson = readPackageMetadata(packageJsonPath);
|
|
31
|
+
return typeof packageJson?.version === "string" ? packageJson.version : void 0;
|
|
32
|
+
};
|
|
33
|
+
const readPackageMcpCompatibilityVersion = (packageJsonPath) => {
|
|
34
|
+
const packageJson = readPackageMetadata(packageJsonPath);
|
|
35
|
+
const version = packageJson?.oceanBrain?.mcpCompatibilityVersion;
|
|
36
|
+
return typeof version === "string" ? version : void 0;
|
|
37
|
+
};
|
|
38
|
+
const createMissingMcpCompatibilityVersionError = () => new Error("Ocean Brain MCP compatibility version is required in package metadata.");
|
|
39
|
+
const getVersionCandidatePaths = () => {
|
|
40
|
+
return Array.from(
|
|
41
|
+
/* @__PURE__ */ new Set([
|
|
42
|
+
path.resolve(paths.packageRoot, "..", "package.json"),
|
|
43
|
+
path.resolve(paths.packageRoot, "..", "cli", "package.json"),
|
|
44
|
+
path.resolve(process.cwd(), "packages", "cli", "package.json"),
|
|
45
|
+
path.resolve(process.cwd(), "..", "cli", "package.json")
|
|
46
|
+
])
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
const resolveOceanBrainVersion = () => {
|
|
50
|
+
for (const candidatePath of getVersionCandidatePaths()) {
|
|
51
|
+
const version = readPackageVersion(candidatePath);
|
|
52
|
+
if (version) {
|
|
53
|
+
return normalizeVersion(version);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (process.env.OCEAN_BRAIN_VERSION) {
|
|
57
|
+
return normalizeVersion(process.env.OCEAN_BRAIN_VERSION);
|
|
58
|
+
}
|
|
59
|
+
return "0.0.0";
|
|
60
|
+
};
|
|
61
|
+
const resolveMcpCompatibilityVersionFromPaths = (candidatePaths) => {
|
|
62
|
+
for (const candidatePath of candidatePaths) {
|
|
63
|
+
const version = readPackageMcpCompatibilityVersion(candidatePath);
|
|
64
|
+
if (version) {
|
|
65
|
+
return normalizeVersion(version);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw createMissingMcpCompatibilityVersionError();
|
|
69
|
+
};
|
|
70
|
+
const resolveMcpCompatibilityVersion = () => resolveMcpCompatibilityVersionFromPaths(getVersionCandidatePaths());
|
|
71
|
+
const buildOceanBrainVersionInfo = ({
|
|
72
|
+
mcpCompatibilityVersion,
|
|
73
|
+
version
|
|
74
|
+
}) => {
|
|
75
|
+
const compatibilityVersion = normalizeVersion(mcpCompatibilityVersion);
|
|
76
|
+
const compatibilityRequirement = formatMcpVersionRequirement(compatibilityVersion);
|
|
77
|
+
return {
|
|
78
|
+
version: normalizeVersion(version),
|
|
79
|
+
releaseUrl: OCEAN_BRAIN_RELEASES_URL,
|
|
80
|
+
mcpVersionRequirement: compatibilityRequirement,
|
|
81
|
+
mcp: {
|
|
82
|
+
compatibilityVersion,
|
|
83
|
+
compatibilityRequirement,
|
|
84
|
+
compatibilityVersionHeader: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
|
|
85
|
+
clientVersionHeader: OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const getOceanBrainVersionInfo = () => buildOceanBrainVersionInfo({
|
|
90
|
+
version: resolveOceanBrainVersion(),
|
|
91
|
+
mcpCompatibilityVersion: resolveMcpCompatibilityVersion()
|
|
92
|
+
});
|
|
93
|
+
export {
|
|
94
|
+
OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
|
|
95
|
+
OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
|
|
96
|
+
OCEAN_BRAIN_RELEASES_URL,
|
|
97
|
+
buildOceanBrainVersionInfo,
|
|
98
|
+
formatMcpVersionRequirement,
|
|
99
|
+
getOceanBrainVersionInfo,
|
|
100
|
+
parseMajorMinorVersion,
|
|
101
|
+
resolveMcpCompatibilityVersion,
|
|
102
|
+
resolveMcpCompatibilityVersionFromPaths,
|
|
103
|
+
resolveOceanBrainVersion
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=app-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/modules/app-version.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { paths } from '~/paths.js';\n\nexport const OCEAN_BRAIN_RELEASES_URL = 'https://github.com/baealex/ocean-brain/releases';\nexport const OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = 'X-Ocean-Brain-MCP-Compatibility-Version';\nexport const OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = 'X-Ocean-Brain-MCP-Client-Version';\n\ninterface MajorMinorVersion {\n major: number;\n minor: number;\n}\n\ninterface PackageMetadata {\n oceanBrain?: {\n mcpCompatibilityVersion?: unknown;\n };\n version?: unknown;\n}\n\nexport interface OceanBrainVersionInfo {\n version: string;\n releaseUrl: string;\n mcpVersionRequirement: string;\n mcp: {\n compatibilityVersion: string;\n compatibilityRequirement: string;\n compatibilityVersionHeader: string;\n clientVersionHeader: string;\n };\n}\n\nconst normalizeVersion = (version: string) => version.trim().replace(/^v/i, '');\n\nexport const parseMajorMinorVersion = (version: string): MajorMinorVersion | null => {\n const match = normalizeVersion(version).match(/^(\\d+)\\.(\\d+)(?:\\.\\d+)?(?:[-+].*)?$/);\n\n if (!match) {\n return null;\n }\n\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n };\n};\n\nexport const formatMcpVersionRequirement = (mcpCompatibilityVersion: string) => {\n const parsed = parseMajorMinorVersion(mcpCompatibilityVersion);\n return parsed ? `${parsed.major}.${parsed.minor}.x` : 'unknown';\n};\n\nconst readPackageMetadata = (packageJsonPath: string): PackageMetadata | undefined => {\n try {\n return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageMetadata;\n } catch {\n return undefined;\n }\n};\n\nconst readPackageVersion = (packageJsonPath: string) => {\n const packageJson = readPackageMetadata(packageJsonPath);\n return typeof packageJson?.version === 'string' ? packageJson.version : undefined;\n};\n\nconst readPackageMcpCompatibilityVersion = (packageJsonPath: string) => {\n const packageJson = readPackageMetadata(packageJsonPath);\n const version = packageJson?.oceanBrain?.mcpCompatibilityVersion;\n return typeof version === 'string' ? version : undefined;\n};\n\nconst createMissingMcpCompatibilityVersionError = () =>\n new Error('Ocean Brain MCP compatibility version is required in package metadata.');\n\nconst getVersionCandidatePaths = () => {\n return Array.from(\n new Set([\n path.resolve(paths.packageRoot, '..', 'package.json'),\n path.resolve(paths.packageRoot, '..', 'cli', 'package.json'),\n path.resolve(process.cwd(), 'packages', 'cli', 'package.json'),\n path.resolve(process.cwd(), '..', 'cli', 'package.json'),\n ]),\n );\n};\n\nexport const resolveOceanBrainVersion = () => {\n for (const candidatePath of getVersionCandidatePaths()) {\n const version = readPackageVersion(candidatePath);\n if (version) {\n return normalizeVersion(version);\n }\n }\n\n if (process.env.OCEAN_BRAIN_VERSION) {\n return normalizeVersion(process.env.OCEAN_BRAIN_VERSION);\n }\n\n return '0.0.0';\n};\n\nexport const resolveMcpCompatibilityVersionFromPaths = (candidatePaths: string[]) => {\n for (const candidatePath of candidatePaths) {\n const version = readPackageMcpCompatibilityVersion(candidatePath);\n if (version) {\n return normalizeVersion(version);\n }\n }\n\n throw createMissingMcpCompatibilityVersionError();\n};\n\nexport const resolveMcpCompatibilityVersion = () => resolveMcpCompatibilityVersionFromPaths(getVersionCandidatePaths());\n\nexport const buildOceanBrainVersionInfo = ({\n mcpCompatibilityVersion,\n version,\n}: {\n mcpCompatibilityVersion: string;\n version: string;\n}): OceanBrainVersionInfo => {\n const compatibilityVersion = normalizeVersion(mcpCompatibilityVersion);\n const compatibilityRequirement = formatMcpVersionRequirement(compatibilityVersion);\n\n return {\n version: normalizeVersion(version),\n releaseUrl: OCEAN_BRAIN_RELEASES_URL,\n mcpVersionRequirement: compatibilityRequirement,\n mcp: {\n compatibilityVersion,\n compatibilityRequirement,\n compatibilityVersionHeader: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,\n clientVersionHeader: OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,\n },\n };\n};\n\nexport const getOceanBrainVersionInfo = () =>\n buildOceanBrainVersionInfo({\n version: resolveOceanBrainVersion(),\n mcpCompatibilityVersion: resolveMcpCompatibilityVersion(),\n });\n"],"mappings":"AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,SAAS,aAAa;AAEf,MAAM,2BAA2B;AACjC,MAAM,+CAA+C;AACrD,MAAM,wCAAwC;AA0BrD,MAAM,mBAAmB,CAAC,YAAoB,QAAQ,KAAK,EAAE,QAAQ,OAAO,EAAE;AAEvE,MAAM,yBAAyB,CAAC,YAA8C;AACjF,QAAM,QAAQ,iBAAiB,OAAO,EAAE,MAAM,qCAAqC;AAEnF,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,IACtB,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,EAC1B;AACJ;AAEO,MAAM,8BAA8B,CAAC,4BAAoC;AAC5E,QAAM,SAAS,uBAAuB,uBAAuB;AAC7D,SAAO,SAAS,GAAG,OAAO,KAAK,IAAI,OAAO,KAAK,OAAO;AAC1D;AAEA,MAAM,sBAAsB,CAAC,oBAAyD;AAClF,MAAI;AACA,WAAO,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;AAAA,EAC/D,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAEA,MAAM,qBAAqB,CAAC,oBAA4B;AACpD,QAAM,cAAc,oBAAoB,eAAe;AACvD,SAAO,OAAO,aAAa,YAAY,WAAW,YAAY,UAAU;AAC5E;AAEA,MAAM,qCAAqC,CAAC,oBAA4B;AACpE,QAAM,cAAc,oBAAoB,eAAe;AACvD,QAAM,UAAU,aAAa,YAAY;AACzC,SAAO,OAAO,YAAY,WAAW,UAAU;AACnD;AAEA,MAAM,4CAA4C,MAC9C,IAAI,MAAM,wEAAwE;AAEtF,MAAM,2BAA2B,MAAM;AACnC,SAAO,MAAM;AAAA,IACT,oBAAI,IAAI;AAAA,MACJ,KAAK,QAAQ,MAAM,aAAa,MAAM,cAAc;AAAA,MACpD,KAAK,QAAQ,MAAM,aAAa,MAAM,OAAO,cAAc;AAAA,MAC3D,KAAK,QAAQ,QAAQ,IAAI,GAAG,YAAY,OAAO,cAAc;AAAA,MAC7D,KAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM,OAAO,cAAc;AAAA,IAC3D,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,2BAA2B,MAAM;AAC1C,aAAW,iBAAiB,yBAAyB,GAAG;AACpD,UAAM,UAAU,mBAAmB,aAAa;AAChD,QAAI,SAAS;AACT,aAAO,iBAAiB,OAAO;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,QAAQ,IAAI,qBAAqB;AACjC,WAAO,iBAAiB,QAAQ,IAAI,mBAAmB;AAAA,EAC3D;AAEA,SAAO;AACX;AAEO,MAAM,0CAA0C,CAAC,mBAA6B;AACjF,aAAW,iBAAiB,gBAAgB;AACxC,UAAM,UAAU,mCAAmC,aAAa;AAChE,QAAI,SAAS;AACT,aAAO,iBAAiB,OAAO;AAAA,IACnC;AAAA,EACJ;AAEA,QAAM,0CAA0C;AACpD;AAEO,MAAM,iCAAiC,MAAM,wCAAwC,yBAAyB,CAAC;AAE/G,MAAM,6BAA6B,CAAC;AAAA,EACvC;AAAA,EACA;AACJ,MAG6B;AACzB,QAAM,uBAAuB,iBAAiB,uBAAuB;AACrE,QAAM,2BAA2B,4BAA4B,oBAAoB;AAEjF,SAAO;AAAA,IACH,SAAS,iBAAiB,OAAO;AAAA,IACjC,YAAY;AAAA,IACZ,uBAAuB;AAAA,IACvB,KAAK;AAAA,MACD;AAAA,MACA;AAAA,MACA,4BAA4B;AAAA,MAC5B,qBAAqB;AAAA,IACzB;AAAA,EACJ;AACJ;AAEO,MAAM,2BAA2B,MACpC,2BAA2B;AAAA,EACvB,SAAS,yBAAyB;AAAA,EAClC,yBAAyB,+BAA+B;AAC5D,CAAC;","names":[]}
|
|
@@ -2,6 +2,8 @@ import { buildUnauthorizedGraphqlPayload, buildUnauthorizedPayload } from "@baej
|
|
|
2
2
|
import session from "express-session";
|
|
3
3
|
import { GraphQLError } from "graphql";
|
|
4
4
|
import lusca from "lusca";
|
|
5
|
+
import { sanitizeRedirectPath } from "./auth-redirect.js";
|
|
6
|
+
import { AUTH_SESSION_IDLE_TIMEOUT_MS, createSessionStore } from "./session-store.js";
|
|
5
7
|
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
6
8
|
const isAuthenticatedRequest = (req) => Boolean(req.session?.authenticated);
|
|
7
9
|
const createSessionMiddleware = (authConfig) => {
|
|
@@ -11,9 +13,12 @@ const createSessionMiddleware = (authConfig) => {
|
|
|
11
13
|
return session({
|
|
12
14
|
secret: authConfig.sessionSecret,
|
|
13
15
|
name: authConfig.cookieName,
|
|
16
|
+
store: createSessionStore(),
|
|
14
17
|
resave: false,
|
|
15
18
|
saveUninitialized: false,
|
|
19
|
+
rolling: true,
|
|
16
20
|
cookie: {
|
|
21
|
+
maxAge: AUTH_SESSION_IDLE_TIMEOUT_MS,
|
|
17
22
|
httpOnly: true,
|
|
18
23
|
sameSite: "lax",
|
|
19
24
|
secure: process.env.NODE_ENV === "production",
|
|
@@ -33,10 +38,23 @@ const createCsrfProtection = (authConfig) => {
|
|
|
33
38
|
sameSite: "lax",
|
|
34
39
|
secure: process.env.NODE_ENV === "production"
|
|
35
40
|
}
|
|
36
|
-
}
|
|
37
|
-
blocklist: ["/api/mcp", "/graphql/mcp"]
|
|
41
|
+
}
|
|
38
42
|
});
|
|
39
43
|
};
|
|
44
|
+
const isCsrfTokenError = (error) => error instanceof Error && error.message.startsWith("CSRF token ");
|
|
45
|
+
const buildLoginRedirectPath = (req) => {
|
|
46
|
+
const nextPath = sanitizeRedirectPath(req.body?.next);
|
|
47
|
+
return `/login?next=${encodeURIComponent(nextPath)}`;
|
|
48
|
+
};
|
|
49
|
+
const createLoginCsrfFailureHandler = (authConfig) => {
|
|
50
|
+
return (error, req, res, next) => {
|
|
51
|
+
if (authConfig.mode !== "password" || !isCsrfTokenError(error) || req.method !== "POST" || req.path !== "/login" || isAuthenticatedRequest(req)) {
|
|
52
|
+
next(error);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
res.redirect(303, buildLoginRedirectPath(req));
|
|
56
|
+
};
|
|
57
|
+
};
|
|
40
58
|
const requireSessionForWrite = (authConfig) => {
|
|
41
59
|
return (req, res, next) => {
|
|
42
60
|
if (authConfig.mode === "open" || isAuthenticatedRequest(req)) {
|
|
@@ -77,6 +95,7 @@ const createMutationAuthValidationRule = () => {
|
|
|
77
95
|
};
|
|
78
96
|
export {
|
|
79
97
|
createCsrfProtection,
|
|
98
|
+
createLoginCsrfFailureHandler,
|
|
80
99
|
createMutationAuthValidationRule,
|
|
81
100
|
createSessionMiddleware,
|
|
82
101
|
isAuthenticatedRequest,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/auth-guard.ts"],"sourcesContent":["import { buildUnauthorizedGraphqlPayload, buildUnauthorizedPayload } from '@baejino/auth';\nimport type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport session from 'express-session';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\nimport lusca from 'lusca';\
|
|
1
|
+
{"version":3,"sources":["../../src/modules/auth-guard.ts"],"sourcesContent":["import { buildUnauthorizedGraphqlPayload, buildUnauthorizedPayload } from '@baejino/auth';\nimport type { ErrorRequestHandler, NextFunction, Request, RequestHandler, Response } from 'express';\nimport session from 'express-session';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\nimport lusca from 'lusca';\nimport type { AuthConfig } from './auth-mode.js';\nimport { sanitizeRedirectPath } from './auth-redirect.js';\nimport { AUTH_SESSION_IDLE_TIMEOUT_MS, createSessionStore } from './session-store.js';\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nexport const isAuthenticatedRequest = (req: Request) => Boolean(req.session?.authenticated);\n\nexport const createSessionMiddleware = (authConfig: AuthConfig): RequestHandler => {\n if (authConfig.mode !== 'password') {\n return (_req, _res, next) => next();\n }\n\n return session({\n secret: authConfig.sessionSecret,\n name: authConfig.cookieName,\n store: createSessionStore(),\n resave: false,\n saveUninitialized: false,\n rolling: true,\n cookie: {\n maxAge: AUTH_SESSION_IDLE_TIMEOUT_MS,\n httpOnly: true,\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n path: '/',\n },\n });\n};\n\nexport const createCsrfProtection = (authConfig: AuthConfig): RequestHandler => {\n if (authConfig.mode !== 'password') {\n return (_req, _res, next) => next();\n }\n\n return lusca.csrf({\n angular: true,\n cookie: {\n options: {\n path: '/',\n sameSite: 'lax',\n secure: process.env.NODE_ENV === 'production',\n },\n },\n });\n};\n\nconst isCsrfTokenError = (error: unknown) => error instanceof Error && error.message.startsWith('CSRF token ');\n\nconst buildLoginRedirectPath = (req: Request) => {\n const nextPath = sanitizeRedirectPath(req.body?.next);\n return `/login?next=${encodeURIComponent(nextPath)}`;\n};\n\nexport const createLoginCsrfFailureHandler = (authConfig: AuthConfig): ErrorRequestHandler => {\n return (error, req, res, next) => {\n if (\n authConfig.mode !== 'password' ||\n !isCsrfTokenError(error) ||\n req.method !== 'POST' ||\n req.path !== '/login' ||\n isAuthenticatedRequest(req)\n ) {\n next(error);\n return;\n }\n\n res.redirect(303, buildLoginRedirectPath(req));\n };\n};\n\nexport const requireSessionForWrite = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'open' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.status(401).set(JSON_HEADERS).json(buildUnauthorizedPayload()).end();\n };\n};\n\nexport const requireSessionForGraphql = (authConfig: AuthConfig): RequestHandler => {\n return (req: Request, res: Response, next: NextFunction) => {\n if (authConfig.mode === 'open' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.status(401).set(JSON_HEADERS).json(buildUnauthorizedGraphqlPayload()).end();\n };\n};\n\nexport const createMutationAuthValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation !== 'mutation') {\n return;\n }\n\n const unauthorizedError = buildUnauthorizedGraphqlPayload().errors[0];\n\n context.reportError(\n new GraphQLError(unauthorizedError.message, {\n nodes: [node],\n extensions: {\n code: unauthorizedError.extensions.code,\n },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { sanitizeRedirectPath as sanitizeCommonRedirectPath } from "@baejino/auth";
|
|
2
|
+
const sanitizeRedirectPath = (value) => sanitizeCommonRedirectPath(value, {
|
|
3
|
+
fallbackPath: "/",
|
|
4
|
+
loginPath: "/login",
|
|
5
|
+
allowedAbsoluteHosts: ["localhost", "127.0.0.1", "::1"]
|
|
6
|
+
});
|
|
7
|
+
export {
|
|
8
|
+
sanitizeRedirectPath
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=auth-redirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/modules/auth-redirect.ts"],"sourcesContent":["import { sanitizeRedirectPath as sanitizeCommonRedirectPath } from '@baejino/auth';\n\nexport const sanitizeRedirectPath = (value: unknown) =>\n sanitizeCommonRedirectPath(value, {\n fallbackPath: '/',\n loginPath: '/login',\n allowedAbsoluteHosts: ['localhost', '127.0.0.1', '::1'],\n });\n"],"mappings":"AAAA,SAAS,wBAAwB,kCAAkC;AAE5D,MAAM,uBAAuB,CAAC,UACjC,2BAA2B,OAAO;AAAA,EAC9B,cAAc;AAAA,EACd,WAAW;AAAA,EACX,sBAAsB,CAAC,aAAa,aAAa,KAAK;AAC1D,CAAC;","names":[]}
|
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import { GraphQLError } from "graphql";
|
|
2
|
+
import {
|
|
3
|
+
getOceanBrainVersionInfo,
|
|
4
|
+
OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
|
|
5
|
+
OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
|
|
6
|
+
parseMajorMinorVersion
|
|
7
|
+
} from "./app-version.js";
|
|
2
8
|
const JSON_HEADERS = { "Content-Type": "application/json" };
|
|
9
|
+
const OCEAN_BRAIN_MCP_VERSION_HEADER = "X-Ocean-Brain-MCP-Version";
|
|
10
|
+
const readHeaderValue = (req, headerName) => {
|
|
11
|
+
const value = req.headers[headerName.toLowerCase()];
|
|
12
|
+
return Array.isArray(value) ? value[0] : value;
|
|
13
|
+
};
|
|
14
|
+
const readMcpCompatibilityVersion = (req) => readHeaderValue(req, OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);
|
|
15
|
+
const readMcpClientVersion = (req) => readHeaderValue(req, OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);
|
|
16
|
+
const isMcpVersionCompatible = (requiredMcpCompatibilityVersion, mcpCompatibilityVersion) => {
|
|
17
|
+
const required = parseMajorMinorVersion(requiredMcpCompatibilityVersion);
|
|
18
|
+
const mcp = parseMajorMinorVersion(mcpCompatibilityVersion);
|
|
19
|
+
return Boolean(required && mcp && required.major === mcp.major && required.minor === mcp.minor);
|
|
20
|
+
};
|
|
21
|
+
const createMcpVersionCompatibilityMessage = ({
|
|
22
|
+
mcpClientVersion,
|
|
23
|
+
mcpCompatibilityVersion,
|
|
24
|
+
requiredMcpCompatibilityVersion,
|
|
25
|
+
serverVersion
|
|
26
|
+
}) => {
|
|
27
|
+
const mcpCompatibilityVersionText = mcpCompatibilityVersion ? `v${mcpCompatibilityVersion}` : "not provided";
|
|
28
|
+
const mcpClientVersionText = mcpClientVersion ? `v${mcpClientVersion}` : "not provided";
|
|
29
|
+
return [
|
|
30
|
+
"Ocean Brain MCP compatibility versions are incompatible.",
|
|
31
|
+
`Server: v${serverVersion}`,
|
|
32
|
+
`Required MCP compatibility: ${requiredMcpCompatibilityVersion}`,
|
|
33
|
+
`MCP compatibility: ${mcpCompatibilityVersionText}`,
|
|
34
|
+
`MCP client: ${mcpClientVersionText}`,
|
|
35
|
+
"",
|
|
36
|
+
"Please update Ocean Brain MCP to a compatible release.",
|
|
37
|
+
getOceanBrainVersionInfo().releaseUrl
|
|
38
|
+
].join("\n");
|
|
39
|
+
};
|
|
3
40
|
const readBearerToken = (authorizationHeader) => {
|
|
4
41
|
if (!authorizationHeader?.startsWith("Bearer ")) {
|
|
5
42
|
return void 0;
|
|
@@ -40,6 +77,28 @@ const createMcpAuthMiddleware = (_authConfig, mcpAdminAuth) => {
|
|
|
40
77
|
}).end();
|
|
41
78
|
return;
|
|
42
79
|
}
|
|
80
|
+
const versionInfo = getOceanBrainVersionInfo();
|
|
81
|
+
const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);
|
|
82
|
+
const mcpClientVersion = readMcpClientVersion(req);
|
|
83
|
+
if (!mcpCompatibilityVersion || !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)) {
|
|
84
|
+
res.status(426).set(JSON_HEADERS).json({
|
|
85
|
+
code: "MCP_VERSION_INCOMPATIBLE",
|
|
86
|
+
message: createMcpVersionCompatibilityMessage({
|
|
87
|
+
serverVersion: versionInfo.version,
|
|
88
|
+
requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
|
|
89
|
+
mcpCompatibilityVersion,
|
|
90
|
+
mcpClientVersion
|
|
91
|
+
}),
|
|
92
|
+
serverVersion: versionInfo.version,
|
|
93
|
+
mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,
|
|
94
|
+
mcpClientVersion: mcpClientVersion ?? null,
|
|
95
|
+
mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,
|
|
96
|
+
requiredMcpVersion: versionInfo.mcpVersionRequirement,
|
|
97
|
+
requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
|
|
98
|
+
releaseUrl: versionInfo.releaseUrl
|
|
99
|
+
}).end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
43
102
|
next();
|
|
44
103
|
} catch (error) {
|
|
45
104
|
next(error);
|
|
@@ -64,7 +123,9 @@ const createReadOnlyMcpValidationRule = () => {
|
|
|
64
123
|
};
|
|
65
124
|
};
|
|
66
125
|
export {
|
|
126
|
+
OCEAN_BRAIN_MCP_VERSION_HEADER,
|
|
67
127
|
createMcpAuthMiddleware,
|
|
68
|
-
createReadOnlyMcpValidationRule
|
|
128
|
+
createReadOnlyMcpValidationRule,
|
|
129
|
+
isMcpVersionCompatible
|
|
69
130
|
};
|
|
70
131
|
//# sourceMappingURL=mcp-auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AAEA,SAAS,oBAAoB;
|
|
1
|
+
{"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport {\n getOceanBrainVersionInfo,\n OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,\n OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,\n parseMajorMinorVersion,\n} from './app-version.js';\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\nexport const OCEAN_BRAIN_MCP_VERSION_HEADER = 'X-Ocean-Brain-MCP-Version';\n\nconst readHeaderValue = (req: Request, headerName: string) => {\n const value = req.headers[headerName.toLowerCase()];\n return Array.isArray(value) ? value[0] : value;\n};\n\nconst readMcpCompatibilityVersion = (req: Request) =>\n readHeaderValue(req, OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER) ??\n readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);\n\nconst readMcpClientVersion = (req: Request) =>\n readHeaderValue(req, OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);\n\nexport const isMcpVersionCompatible = (requiredMcpCompatibilityVersion: string, mcpCompatibilityVersion: string) => {\n const required = parseMajorMinorVersion(requiredMcpCompatibilityVersion);\n const mcp = parseMajorMinorVersion(mcpCompatibilityVersion);\n\n return Boolean(required && mcp && required.major === mcp.major && required.minor === mcp.minor);\n};\n\nconst createMcpVersionCompatibilityMessage = ({\n mcpClientVersion,\n mcpCompatibilityVersion,\n requiredMcpCompatibilityVersion,\n serverVersion,\n}: {\n mcpClientVersion?: string;\n mcpCompatibilityVersion?: string;\n requiredMcpCompatibilityVersion: string;\n serverVersion: string;\n}) => {\n const mcpCompatibilityVersionText = mcpCompatibilityVersion ? `v${mcpCompatibilityVersion}` : 'not provided';\n const mcpClientVersionText = mcpClientVersion ? `v${mcpClientVersion}` : 'not provided';\n\n return [\n 'Ocean Brain MCP compatibility versions are incompatible.',\n `Server: v${serverVersion}`,\n `Required MCP compatibility: ${requiredMcpCompatibilityVersion}`,\n `MCP compatibility: ${mcpCompatibilityVersionText}`,\n `MCP client: ${mcpClientVersionText}`,\n '',\n 'Please update Ocean Brain MCP to a compatible release.',\n getOceanBrainVersionInfo().releaseUrl,\n ].join('\\n');\n};\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n const versionInfo = getOceanBrainVersionInfo();\n const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);\n const mcpClientVersion = readMcpClientVersion(req);\n\n if (\n !mcpCompatibilityVersion ||\n !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)\n ) {\n res.status(426)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_VERSION_INCOMPATIBLE',\n message: createMcpVersionCompatibilityMessage({\n serverVersion: versionInfo.version,\n requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,\n mcpCompatibilityVersion,\n mcpClientVersion,\n }),\n serverVersion: versionInfo.version,\n mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,\n mcpClientVersion: mcpClientVersion ?? null,\n mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,\n requiredMcpVersion: versionInfo.mcpVersionRequirement,\n requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,\n releaseUrl: versionInfo.releaseUrl,\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { rateLimit } from "express-rate-limit";
|
|
2
2
|
const AUTH_RATE_LIMIT_MESSAGE = "Too many authentication attempts. Please try again later.";
|
|
3
|
+
const SESSION_ACCESS_RATE_LIMIT_MESSAGE = "Too many authenticated requests. Please try again later.";
|
|
3
4
|
const createAuthAttemptRateLimit = () => rateLimit({
|
|
4
5
|
windowMs: 15 * 60 * 1e3,
|
|
5
6
|
limit: 10,
|
|
@@ -12,7 +13,20 @@ const createAuthAttemptRateLimit = () => rateLimit({
|
|
|
12
13
|
});
|
|
13
14
|
}
|
|
14
15
|
});
|
|
16
|
+
const createSessionAccessRateLimit = () => rateLimit({
|
|
17
|
+
windowMs: 60 * 1e3,
|
|
18
|
+
limit: 300,
|
|
19
|
+
standardHeaders: true,
|
|
20
|
+
legacyHeaders: false,
|
|
21
|
+
handler: (_req, res) => {
|
|
22
|
+
res.status(429).json({
|
|
23
|
+
code: "SESSION_RATE_LIMITED",
|
|
24
|
+
message: SESSION_ACCESS_RATE_LIMIT_MESSAGE
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
15
28
|
export {
|
|
16
|
-
createAuthAttemptRateLimit
|
|
29
|
+
createAuthAttemptRateLimit,
|
|
30
|
+
createSessionAccessRateLimit
|
|
17
31
|
};
|
|
18
32
|
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/rate-limit.ts"],"sourcesContent":["import { rateLimit } from 'express-rate-limit';\n\nconst AUTH_RATE_LIMIT_MESSAGE = 'Too many authentication attempts. Please try again later.';\n\nexport const createAuthAttemptRateLimit = () =>\n rateLimit({\n windowMs: 15 * 60 * 1000,\n limit: 10,\n standardHeaders: true,\n legacyHeaders: false,\n handler: (_req, res) => {\n res.status(429).json({\n code: 'AUTH_RATE_LIMITED',\n message: AUTH_RATE_LIMIT_MESSAGE,\n });\n },\n });\n"],"mappings":"AAAA,SAAS,iBAAiB;AAE1B,MAAM,0BAA0B;
|
|
1
|
+
{"version":3,"sources":["../../src/modules/rate-limit.ts"],"sourcesContent":["import { rateLimit } from 'express-rate-limit';\n\nconst AUTH_RATE_LIMIT_MESSAGE = 'Too many authentication attempts. Please try again later.';\nconst SESSION_ACCESS_RATE_LIMIT_MESSAGE = 'Too many authenticated requests. Please try again later.';\n\nexport const createAuthAttemptRateLimit = () =>\n rateLimit({\n windowMs: 15 * 60 * 1000,\n limit: 10,\n standardHeaders: true,\n legacyHeaders: false,\n handler: (_req, res) => {\n res.status(429).json({\n code: 'AUTH_RATE_LIMITED',\n message: AUTH_RATE_LIMIT_MESSAGE,\n });\n },\n });\n\nexport const createSessionAccessRateLimit = () =>\n rateLimit({\n windowMs: 60 * 1000,\n limit: 300,\n standardHeaders: true,\n legacyHeaders: false,\n handler: (_req, res) => {\n res.status(429).json({\n code: 'SESSION_RATE_LIMITED',\n message: SESSION_ACCESS_RATE_LIMIT_MESSAGE,\n });\n },\n });\n"],"mappings":"AAAA,SAAS,iBAAiB;AAE1B,MAAM,0BAA0B;AAChC,MAAM,oCAAoC;AAEnC,MAAM,6BAA6B,MACtC,UAAU;AAAA,EACN,UAAU,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;AAEE,MAAM,+BAA+B,MACxC,UAAU;AAAA,EACN,UAAU,KAAK;AAAA,EACf,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;","names":[]}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { serializeServerEvent, subscribeServerEvents } from "./server-events.js";
|
|
2
|
+
import { AUTH_SESSION_IDLE_TIMEOUT_MS } from "./session-store.js";
|
|
2
3
|
const KEEP_ALIVE_INTERVAL_MS = 3e4;
|
|
4
|
+
const getEventStreamLifetimeMs = (expires) => {
|
|
5
|
+
if (!expires) {
|
|
6
|
+
return AUTH_SESSION_IDLE_TIMEOUT_MS;
|
|
7
|
+
}
|
|
8
|
+
const expiresAt = expires instanceof Date ? expires : new Date(expires);
|
|
9
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
10
|
+
return AUTH_SESSION_IDLE_TIMEOUT_MS;
|
|
11
|
+
}
|
|
12
|
+
return Math.max(0, Math.min(expiresAt.getTime() - Date.now(), AUTH_SESSION_IDLE_TIMEOUT_MS));
|
|
13
|
+
};
|
|
3
14
|
const createServerEventsHandler = () => {
|
|
4
15
|
return async (req, res) => {
|
|
5
16
|
res.status(200);
|
|
@@ -15,6 +26,11 @@ const createServerEventsHandler = () => {
|
|
|
15
26
|
const keepAliveTimer = setInterval(() => {
|
|
16
27
|
res.write(": keepalive\n\n");
|
|
17
28
|
}, KEEP_ALIVE_INTERVAL_MS);
|
|
29
|
+
keepAliveTimer.unref?.();
|
|
30
|
+
const sessionExpiryTimer = setTimeout(() => {
|
|
31
|
+
res.end();
|
|
32
|
+
}, getEventStreamLifetimeMs(req.session?.cookie.expires));
|
|
33
|
+
sessionExpiryTimer.unref?.();
|
|
18
34
|
let cleanedUp = false;
|
|
19
35
|
const cleanup = () => {
|
|
20
36
|
if (cleanedUp) {
|
|
@@ -22,6 +38,7 @@ const createServerEventsHandler = () => {
|
|
|
22
38
|
}
|
|
23
39
|
cleanedUp = true;
|
|
24
40
|
clearInterval(keepAliveTimer);
|
|
41
|
+
clearTimeout(sessionExpiryTimer);
|
|
25
42
|
unsubscribe();
|
|
26
43
|
};
|
|
27
44
|
req.on("close", cleanup);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/modules/server-events-handler.ts"],"sourcesContent":["import type { Controller } from '~/types/index.js';\nimport { serializeServerEvent, subscribeServerEvents } from './server-events.js';\n\nconst KEEP_ALIVE_INTERVAL_MS = 30_000;\n\nexport const createServerEventsHandler = (): Controller => {\n return async (req, res) => {\n res.status(200);\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders?.();\n res.write(': connected\\n\\n');\n\n const unsubscribe = subscribeServerEvents((event) => {\n res.write(serializeServerEvent(event));\n });\n\n const keepAliveTimer = setInterval(() => {\n res.write(': keepalive\\n\\n');\n }, KEEP_ALIVE_INTERVAL_MS);\n\n let cleanedUp = false;\n\n const cleanup = () => {\n if (cleanedUp) {\n return;\n }\n\n cleanedUp = true;\n clearInterval(keepAliveTimer);\n unsubscribe();\n };\n\n req.on('close', cleanup);\n res.on('close', cleanup);\n };\n};\n"],"mappings":"AACA,SAAS,sBAAsB,6BAA6B;
|
|
1
|
+
{"version":3,"sources":["../../src/modules/server-events-handler.ts"],"sourcesContent":["import type { Controller } from '~/types/index.js';\nimport { serializeServerEvent, subscribeServerEvents } from './server-events.js';\nimport { AUTH_SESSION_IDLE_TIMEOUT_MS } from './session-store.js';\n\nconst KEEP_ALIVE_INTERVAL_MS = 30_000;\n\nconst getEventStreamLifetimeMs = (expires?: Date | string | null) => {\n if (!expires) {\n return AUTH_SESSION_IDLE_TIMEOUT_MS;\n }\n\n const expiresAt = expires instanceof Date ? expires : new Date(expires);\n\n if (Number.isNaN(expiresAt.getTime())) {\n return AUTH_SESSION_IDLE_TIMEOUT_MS;\n }\n\n return Math.max(0, Math.min(expiresAt.getTime() - Date.now(), AUTH_SESSION_IDLE_TIMEOUT_MS));\n};\n\nexport const createServerEventsHandler = (): Controller => {\n return async (req, res) => {\n res.status(200);\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders?.();\n res.write(': connected\\n\\n');\n\n const unsubscribe = subscribeServerEvents((event) => {\n res.write(serializeServerEvent(event));\n });\n\n const keepAliveTimer = setInterval(() => {\n res.write(': keepalive\\n\\n');\n }, KEEP_ALIVE_INTERVAL_MS);\n keepAliveTimer.unref?.();\n\n const sessionExpiryTimer = setTimeout(() => {\n res.end();\n }, getEventStreamLifetimeMs(req.session?.cookie.expires));\n sessionExpiryTimer.unref?.();\n\n let cleanedUp = false;\n\n const cleanup = () => {\n if (cleanedUp) {\n return;\n }\n\n cleanedUp = true;\n clearInterval(keepAliveTimer);\n clearTimeout(sessionExpiryTimer);\n unsubscribe();\n };\n\n req.on('close', cleanup);\n res.on('close', cleanup);\n };\n};\n"],"mappings":"AACA,SAAS,sBAAsB,6BAA6B;AAC5D,SAAS,oCAAoC;AAE7C,MAAM,yBAAyB;AAE/B,MAAM,2BAA2B,CAAC,YAAmC;AACjE,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,QAAM,YAAY,mBAAmB,OAAO,UAAU,IAAI,KAAK,OAAO;AAEtE,MAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,GAAG;AACnC,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,QAAQ,IAAI,KAAK,IAAI,GAAG,4BAA4B,CAAC;AAC/F;AAEO,MAAM,4BAA4B,MAAkB;AACvD,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,OAAO,GAAG;AACd,QAAI,UAAU,gBAAgB,kCAAkC;AAChE,QAAI,UAAU,iBAAiB,wBAAwB;AACvD,QAAI,UAAU,cAAc,YAAY;AACxC,QAAI,UAAU,qBAAqB,IAAI;AACvC,QAAI,eAAe;AACnB,QAAI,MAAM,iBAAiB;AAE3B,UAAM,cAAc,sBAAsB,CAAC,UAAU;AACjD,UAAI,MAAM,qBAAqB,KAAK,CAAC;AAAA,IACzC,CAAC;AAED,UAAM,iBAAiB,YAAY,MAAM;AACrC,UAAI,MAAM,iBAAiB;AAAA,IAC/B,GAAG,sBAAsB;AACzB,mBAAe,QAAQ;AAEvB,UAAM,qBAAqB,WAAW,MAAM;AACxC,UAAI,IAAI;AAAA,IACZ,GAAG,yBAAyB,IAAI,SAAS,OAAO,OAAO,CAAC;AACxD,uBAAmB,QAAQ;AAE3B,QAAI,YAAY;AAEhB,UAAM,UAAU,MAAM;AAClB,UAAI,WAAW;AACX;AAAA,MACJ;AAEA,kBAAY;AACZ,oBAAc,cAAc;AAC5B,mBAAa,kBAAkB;AAC/B,kBAAY;AAAA,IAChB;AAEA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AAAA,EAC3B;AACJ;","names":[]}
|