collabmd 0.1.31 → 0.1.33
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/client/assets/{drawio-editor-B4sHHwWJ.js → drawio-editor-BMTwP5mD.js} +1 -1
- package/dist/client/assets/drawioEditor-D6clB5OH.js +2 -0
- package/dist/client/assets/{editor-session-BKHEYkbY.js → editor-session-Dzy_85Wp.js} +2 -2
- package/dist/client/assets/embedded-editor-base-CgEkjNYn.css +1 -0
- package/dist/client/assets/{excalidraw-editor-CHbeNZu6.js → excalidraw-editor-CAP9F8CB.js} +3 -3
- package/dist/client/assets/{excalidrawEditor-BhaTX2Ko.js → excalidrawEditor-DD4AvzYP.js} +2 -2
- package/dist/client/assets/exportDocument-BbkN85c4.css +1 -0
- package/dist/client/assets/index-BIt5EZXz.css +1 -0
- package/dist/client/assets/{index-kPgEYvEX.js → index-Czg_KL-i.js} +2 -2
- package/dist/client/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/client/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/client/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/client/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/client/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/client/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/client/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/client/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/client/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/client/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/client/assets/main-DeowJOSp.js +1368 -0
- package/dist/client/assets/vault-api-client-D7Ck0CvR.css +1 -0
- package/dist/client/assets/vault-api-client-p7h7cXKM.js +1 -0
- package/dist/client/drawio-editor.html +1 -1
- package/dist/client/excalidraw-editor.html +1 -1
- package/dist/client/export-document.html +2 -2
- package/dist/client/index.html +2 -2
- package/package.json +8 -5
- package/src/client/app/main-entry.js +2 -0
- package/src/client/application/app-shell/git-feature.js +28 -36
- package/src/client/application/app-shell/presence-feature.js +3 -1
- package/src/client/application/diagram-preview-hydrator.js +24 -15
- package/src/client/application/mermaid-preview-hydrator.js +121 -4
- package/src/client/application/plantuml-preview-hydrator.js +6 -16
- package/src/client/application/preview-renderer.js +11 -2
- package/src/client/bootstrap/collabmd-app-shell.js +182 -50
- package/src/client/domain/excalidraw-document-switch.js +3 -0
- package/src/client/excalidraw-editor.js +187 -84
- package/src/client/infrastructure/auth-client.js +4 -4
- package/src/client/infrastructure/backlinks-api-client.js +18 -0
- package/src/client/infrastructure/comment-thread-store.js +4 -0
- package/src/client/infrastructure/editor-session.js +6 -0
- package/src/client/infrastructure/editor-view-adapter.js +23 -0
- package/src/client/infrastructure/git-api-client.js +134 -0
- package/src/client/infrastructure/plantuml-api-client.js +21 -0
- package/src/client/infrastructure/vault-api-client.js +115 -0
- package/src/client/presentation/backlinks-panel.js +6 -9
- package/src/client/presentation/bases-preview-controller.js +1413 -34
- package/src/client/presentation/comments-panel.js +50 -5
- package/src/client/presentation/excalidraw-embed-controller.js +251 -15
- package/src/client/presentation/excalidraw-embed-reconciler.js +0 -1
- package/src/client/presentation/file-action-controller.js +97 -0
- package/src/client/presentation/file-explorer-controller.js +2 -0
- package/src/client/presentation/file-explorer-view.js +368 -0
- package/src/client/presentation/file-history-view-controller.js +41 -40
- package/src/client/presentation/git-diff-view-controller.js +70 -53
- package/src/client/presentation/git-panel-controller.js +12 -30
- package/src/client/styles/components/scrollbars.css +5 -0
- package/src/client/styles/export-document.css +45 -2
- package/src/client/styles/features/auth-gate.css +13 -24
- package/src/client/styles/features/comment-card.css +6 -0
- package/src/client/styles/features/comments-drawer.css +38 -18
- package/src/client/styles/features/diagram-preview.css +33 -0
- package/src/client/styles/features/embedded-preview.css +37 -11
- package/src/client/styles/features/preview-markdown.css +559 -39
- package/src/client/styles/foundation/themes.css +4 -0
- package/src/client/styles/foundation/tokens.css +4 -3
- package/src/client/styles/layout/sidebar.css +72 -0
- package/src/server/auth/auth-common.js +224 -0
- package/src/server/auth/auth-constants.js +30 -0
- package/src/server/auth/auth-strategies.js +320 -0
- package/src/server/auth/create-auth-service.js +24 -545
- package/src/server/domain/backlink-index.js +177 -5
- package/src/server/domain/bases/base-definition.js +382 -0
- package/src/server/domain/bases/base-expression-runtime.js +1196 -0
- package/src/server/domain/bases/base-index-snapshot-store.js +565 -0
- package/src/server/domain/bases/base-query-metadata.js +242 -0
- package/src/server/domain/bases/base-query-results.js +215 -0
- package/src/server/domain/bases/base-query-service.js +406 -1992
- package/src/server/domain/bases/base-transform.js +116 -0
- package/src/server/domain/collaboration/collaboration-room.js +36 -1
- package/src/server/domain/collaboration/room-client-state-store.js +1 -0
- package/src/server/domain/collaboration/room-persistence-controller.js +1 -1
- package/src/server/infrastructure/http/create-vault-api-query-handler.js +161 -1
- package/src/server/infrastructure/http/http-response.js +56 -0
- package/src/server/infrastructure/persistence/vault-file-store.js +110 -0
- package/dist/client/assets/drawioEditor-DGpo2hcx.js +0 -2
- package/dist/client/assets/embedded-editor-base-DgmRcsR8.css +0 -1
- package/dist/client/assets/exportDocument-DmV7Gngb.css +0 -1
- package/dist/client/assets/index-D69HOZHr.css +0 -1
- package/dist/client/assets/main-DSxNTtD-.js +0 -1243
- package/dist/client/assets/vault-api-client-BzHQseMN.js +0 -1
- package/dist/client/assets/vault-api-client-De7kMvrE.css +0 -1
- /package/dist/client/assets/{exportDocument-CtZKWE2c.js → exportDocument-Zfwq1XVx.js} +0 -0
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
--color-primary: #4f46e5;
|
|
22
22
|
--color-primary-hover: #4338ca;
|
|
23
23
|
--color-primary-active: #3730a3;
|
|
24
|
+
--color-primary-contrast: #f5f5f7;
|
|
24
25
|
--color-primary-highlight: #e0e0ff;
|
|
25
26
|
--color-comment: #4f46e5;
|
|
26
27
|
--color-comment-soft: rgba(79, 70, 229, 0.08);
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
--color-comment-text: #3730a3;
|
|
29
30
|
|
|
30
31
|
--color-error: #dc2626;
|
|
32
|
+
--color-danger: #dc2626;
|
|
31
33
|
--color-error-highlight: #fce8e8;
|
|
32
34
|
--color-success: #16a34a;
|
|
33
35
|
--color-success-highlight: #dcfce7;
|
|
@@ -140,6 +142,7 @@
|
|
|
140
142
|
--color-primary: #818cf8;
|
|
141
143
|
--color-primary-hover: #6366f1;
|
|
142
144
|
--color-primary-active: #4f46e5;
|
|
145
|
+
--color-primary-contrast: #0f1117;
|
|
143
146
|
--color-primary-highlight: #1e1e3a;
|
|
144
147
|
--color-comment: #818cf8;
|
|
145
148
|
--color-comment-soft: rgba(129, 140, 248, 0.1);
|
|
@@ -147,6 +150,7 @@
|
|
|
147
150
|
--color-comment-text: #c7d2fe;
|
|
148
151
|
|
|
149
152
|
--color-error: #f87171;
|
|
153
|
+
--color-danger: #f87171;
|
|
150
154
|
--color-error-highlight: #2a1a1a;
|
|
151
155
|
--color-success: #4ade80;
|
|
152
156
|
--color-success-highlight: #1a2a1a;
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
--text-title-sm: 15px;
|
|
18
18
|
--text-title-md: 18px;
|
|
19
19
|
|
|
20
|
-
--font-body: 'Inter', system-ui, sans-serif;
|
|
21
|
-
--font-display: '
|
|
22
|
-
--font-
|
|
20
|
+
--font-body: 'Inter Variable', 'Inter', system-ui, sans-serif;
|
|
21
|
+
--font-display: 'Inter Variable', 'Inter', system-ui, sans-serif;
|
|
22
|
+
--font-prose: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Noto Sans', 'Liberation Sans', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
23
|
+
--font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', 'SF Mono', monospace;
|
|
23
24
|
|
|
24
25
|
--space-1: 0.25rem;
|
|
25
26
|
--space-2: 0.5rem;
|
|
@@ -118,6 +118,17 @@
|
|
|
118
118
|
overflow-x: hidden;
|
|
119
119
|
overflow-y: auto;
|
|
120
120
|
padding: var(--space-2) 0;
|
|
121
|
+
transition: box-shadow var(--transition-interactive), background var(--transition-interactive);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.file-tree.is-drop-target-root {
|
|
125
|
+
background: color-mix(in oklab, var(--color-primary-highlight) 58%, transparent);
|
|
126
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-primary) 48%, transparent);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.file-tree.is-drop-invalid {
|
|
130
|
+
background: color-mix(in oklab, var(--color-danger) 8%, transparent);
|
|
131
|
+
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-danger) 42%, transparent);
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
.file-tree-empty {
|
|
@@ -127,6 +138,43 @@
|
|
|
127
138
|
font-size: var(--text-xs);
|
|
128
139
|
}
|
|
129
140
|
|
|
141
|
+
.file-tree-root-drop-zone {
|
|
142
|
+
display: block;
|
|
143
|
+
margin: 0 var(--space-2);
|
|
144
|
+
margin-bottom: var(--space-2);
|
|
145
|
+
padding: var(--space-2) var(--space-3);
|
|
146
|
+
border: 1px dashed transparent;
|
|
147
|
+
border-radius: var(--radius-lg);
|
|
148
|
+
background: transparent;
|
|
149
|
+
color: var(--color-text-faint);
|
|
150
|
+
font-size: var(--text-xs);
|
|
151
|
+
line-height: 1.3;
|
|
152
|
+
opacity: 0.88;
|
|
153
|
+
transition:
|
|
154
|
+
opacity var(--transition-interactive),
|
|
155
|
+
border-color var(--transition-interactive),
|
|
156
|
+
background var(--transition-interactive),
|
|
157
|
+
color var(--transition-interactive);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.file-tree-root-drop-zone:hover {
|
|
161
|
+
border-color: color-mix(in oklab, var(--color-border) 85%, transparent);
|
|
162
|
+
background: color-mix(in oklab, var(--color-surface-offset) 72%, transparent);
|
|
163
|
+
color: var(--color-text);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.file-tree-root-drop-zone.is-drop-target {
|
|
167
|
+
border-color: color-mix(in oklab, var(--color-primary) 54%, transparent);
|
|
168
|
+
background: color-mix(in oklab, var(--color-primary-highlight) 72%, transparent);
|
|
169
|
+
color: var(--color-primary);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.file-tree-root-drop-zone.is-drop-invalid {
|
|
173
|
+
border-color: color-mix(in oklab, var(--color-danger) 48%, transparent);
|
|
174
|
+
background: color-mix(in oklab, var(--color-danger) 10%, transparent);
|
|
175
|
+
color: var(--color-danger);
|
|
176
|
+
}
|
|
177
|
+
|
|
130
178
|
.file-tree-item {
|
|
131
179
|
position: relative;
|
|
132
180
|
display: flex;
|
|
@@ -151,6 +199,16 @@
|
|
|
151
199
|
font-size: var(--text-ui-sm);
|
|
152
200
|
}
|
|
153
201
|
|
|
202
|
+
.file-tree-item.is-dragging,
|
|
203
|
+
.file-tree-item[draggable='true']:active {
|
|
204
|
+
cursor: grabbing;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
body.is-file-tree-dragging,
|
|
208
|
+
body.is-file-tree-dragging * {
|
|
209
|
+
cursor: grabbing !important;
|
|
210
|
+
}
|
|
211
|
+
|
|
154
212
|
.file-tree-item:not([data-depth='0'])::before {
|
|
155
213
|
content: '';
|
|
156
214
|
position: absolute;
|
|
@@ -171,6 +229,20 @@
|
|
|
171
229
|
color: var(--color-text);
|
|
172
230
|
}
|
|
173
231
|
|
|
232
|
+
.file-tree-item.is-dragging {
|
|
233
|
+
opacity: 0.52;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.file-tree-dir.is-drop-target {
|
|
237
|
+
background: color-mix(in oklab, var(--color-primary-highlight) 78%, transparent);
|
|
238
|
+
color: var(--color-primary);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.file-tree-dir.is-drop-invalid {
|
|
242
|
+
background: color-mix(in oklab, var(--color-danger) 10%, transparent);
|
|
243
|
+
color: var(--color-danger);
|
|
244
|
+
}
|
|
245
|
+
|
|
174
246
|
.file-tree-file.active {
|
|
175
247
|
background: var(--color-primary-highlight);
|
|
176
248
|
color: var(--color-primary);
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AUTH_STRATEGY_NONE,
|
|
5
|
+
AUTH_STRATEGY_OIDC,
|
|
6
|
+
AUTH_STRATEGY_PASSWORD,
|
|
7
|
+
OIDC_PROVIDER_GOOGLE,
|
|
8
|
+
} from './auth-constants.js';
|
|
9
|
+
|
|
10
|
+
export function hashPassword(value) {
|
|
11
|
+
return createHash('sha256').update(String(value ?? ''), 'utf8').digest();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createResponse(statusCode, body, options = {}) {
|
|
15
|
+
return {
|
|
16
|
+
body,
|
|
17
|
+
kind: options.kind || 'response',
|
|
18
|
+
redirectTo: options.redirectTo || '',
|
|
19
|
+
setCookie: options.setCookie || null,
|
|
20
|
+
statusCode,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createUnauthorizedBody(clientConfig, {
|
|
25
|
+
error = 'Authentication required',
|
|
26
|
+
code = 'AUTH_REQUIRED',
|
|
27
|
+
} = {}) {
|
|
28
|
+
return {
|
|
29
|
+
auth: clientConfig,
|
|
30
|
+
code,
|
|
31
|
+
error,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function prependBasePath(basePath, pathname) {
|
|
36
|
+
if (!basePath) {
|
|
37
|
+
return pathname;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return pathname === '/' ? basePath : `${basePath}${pathname}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function appendHashParam(path, key, value, publicBaseUrl) {
|
|
44
|
+
const url = new URL(path, publicBaseUrl);
|
|
45
|
+
const hashParams = new URLSearchParams(url.hash.startsWith('#') ? url.hash.slice(1) : url.hash);
|
|
46
|
+
hashParams.set(key, value);
|
|
47
|
+
url.hash = hashParams.toString();
|
|
48
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getDefaultReturnTo(basePath) {
|
|
52
|
+
return basePath ? `${basePath}/` : '/';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function sanitizeReturnTo(rawValue, { basePath = '', publicBaseUrl = '' } = {}) {
|
|
56
|
+
const fallback = getDefaultReturnTo(basePath);
|
|
57
|
+
const candidate = String(rawValue ?? '').trim();
|
|
58
|
+
if (!candidate) {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const resolved = new URL(candidate, `${publicBaseUrl || 'http://localhost'}/`);
|
|
64
|
+
const publicOrigin = publicBaseUrl ? new URL(publicBaseUrl).origin : resolved.origin;
|
|
65
|
+
if (resolved.origin !== publicOrigin) {
|
|
66
|
+
return fallback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (basePath) {
|
|
70
|
+
const withinBasePath = resolved.pathname === basePath || resolved.pathname.startsWith(`${basePath}/`);
|
|
71
|
+
if (!withinBasePath) {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
77
|
+
} catch {
|
|
78
|
+
return fallback;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isNonEmptyString(value) {
|
|
83
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function normalizeEmailAddress(value) {
|
|
87
|
+
return String(value ?? '').trim().toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getEmailDomain(email) {
|
|
91
|
+
const normalized = normalizeEmailAddress(email);
|
|
92
|
+
const atIndex = normalized.lastIndexOf('@');
|
|
93
|
+
if (atIndex <= 0 || atIndex === normalized.length - 1) {
|
|
94
|
+
return '';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return normalized.slice(atIndex + 1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function isOidcUserAllowed(oidcConfig, email) {
|
|
101
|
+
const normalizedEmail = normalizeEmailAddress(email);
|
|
102
|
+
const emailDomain = getEmailDomain(normalizedEmail);
|
|
103
|
+
const allowedEmails = Array.isArray(oidcConfig?.allowedEmails) ? oidcConfig.allowedEmails : [];
|
|
104
|
+
const allowedDomains = Array.isArray(oidcConfig?.allowedDomains) ? oidcConfig.allowedDomains : [];
|
|
105
|
+
|
|
106
|
+
if (allowedEmails.length === 0 && allowedDomains.length === 0) {
|
|
107
|
+
return { allowed: true };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (allowedEmails.includes(normalizedEmail)) {
|
|
111
|
+
return { allowed: true, reason: 'email' };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (allowedDomains.includes(emailDomain)) {
|
|
115
|
+
return { allowed: true, reason: 'domain' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
allowed: false,
|
|
120
|
+
error: allowedEmails.length > 0 && allowedDomains.length > 0
|
|
121
|
+
? 'This Google account is not in the allowed email or domain list.'
|
|
122
|
+
: allowedEmails.length > 0
|
|
123
|
+
? 'This Google account is not in the allowed email list.'
|
|
124
|
+
: 'This Google account is not in an allowed domain.',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function hasExpiredSession(session) {
|
|
129
|
+
const expiresAt = Number(session?.expiresAt);
|
|
130
|
+
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function readAuthenticatedOidcSession(sessionCookieManager, req) {
|
|
134
|
+
const session = sessionCookieManager.readSession(req);
|
|
135
|
+
if (session?.strategy !== AUTH_STRATEGY_OIDC) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const user = session.user;
|
|
140
|
+
if (
|
|
141
|
+
!user
|
|
142
|
+
|| typeof user !== 'object'
|
|
143
|
+
|| !isNonEmptyString(user.sub)
|
|
144
|
+
|| !isNonEmptyString(user.email)
|
|
145
|
+
|| !isNonEmptyString(user.name)
|
|
146
|
+
) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (hasExpiredSession(session)) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
...session,
|
|
156
|
+
user: {
|
|
157
|
+
email: user.email,
|
|
158
|
+
emailVerified: user.emailVerified === true,
|
|
159
|
+
name: user.name,
|
|
160
|
+
picture: typeof user.picture === 'string' ? user.picture : '',
|
|
161
|
+
sub: user.sub,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function resolveSessionExpiresAt(sessionMaxAgeMs, fallbackExpiresAt = null) {
|
|
167
|
+
if (Number.isFinite(sessionMaxAgeMs) && sessionMaxAgeMs > 0) {
|
|
168
|
+
return Date.now() + sessionMaxAgeMs;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return Number.isFinite(fallbackExpiresAt) ? fallbackExpiresAt : null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function createSessionCookieOptions(expiresAt) {
|
|
175
|
+
if (!Number.isFinite(expiresAt)) {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
expires: new Date(expiresAt),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function buildClientConfig(authConfig, { basePath = '' } = {}) {
|
|
185
|
+
const loginEndpoint = prependBasePath(basePath, '/api/auth/oidc/login');
|
|
186
|
+
const sessionEndpoint = prependBasePath(basePath, '/api/auth/session');
|
|
187
|
+
const statusEndpoint = prependBasePath(basePath, '/api/auth/status');
|
|
188
|
+
|
|
189
|
+
if (authConfig.strategy === AUTH_STRATEGY_NONE) {
|
|
190
|
+
return {
|
|
191
|
+
enabled: false,
|
|
192
|
+
implemented: true,
|
|
193
|
+
requiresLogin: false,
|
|
194
|
+
sessionEndpoint,
|
|
195
|
+
statusEndpoint,
|
|
196
|
+
strategy: AUTH_STRATEGY_NONE,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (authConfig.strategy === AUTH_STRATEGY_PASSWORD) {
|
|
201
|
+
return {
|
|
202
|
+
enabled: true,
|
|
203
|
+
implemented: true,
|
|
204
|
+
passwordLabel: 'Host password',
|
|
205
|
+
requiresLogin: true,
|
|
206
|
+
sessionEndpoint,
|
|
207
|
+
statusEndpoint,
|
|
208
|
+
strategy: AUTH_STRATEGY_PASSWORD,
|
|
209
|
+
submitLabel: 'Join session',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
enabled: true,
|
|
215
|
+
implemented: true,
|
|
216
|
+
loginEndpoint,
|
|
217
|
+
provider: OIDC_PROVIDER_GOOGLE,
|
|
218
|
+
requiresLogin: true,
|
|
219
|
+
sessionEndpoint,
|
|
220
|
+
statusEndpoint,
|
|
221
|
+
strategy: AUTH_STRATEGY_OIDC,
|
|
222
|
+
submitLabel: 'Continue with Google',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export const AUTH_STRATEGY_NONE = 'none';
|
|
4
|
+
export const AUTH_STRATEGY_PASSWORD = 'password';
|
|
5
|
+
export const AUTH_STRATEGY_OIDC = 'oidc';
|
|
6
|
+
|
|
7
|
+
export const SUPPORTED_AUTH_STRATEGIES = new Set([
|
|
8
|
+
AUTH_STRATEGY_NONE,
|
|
9
|
+
AUTH_STRATEGY_PASSWORD,
|
|
10
|
+
AUTH_STRATEGY_OIDC,
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export const OIDC_FLOW_TTL_MS = 10 * 60 * 1000;
|
|
14
|
+
export const OIDC_PROVIDER_GOOGLE = 'google';
|
|
15
|
+
|
|
16
|
+
export function createRandomAuthPassword(length = 18) {
|
|
17
|
+
const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789';
|
|
18
|
+
const bytes = randomBytes(length);
|
|
19
|
+
let password = '';
|
|
20
|
+
|
|
21
|
+
for (let index = 0; index < length; index += 1) {
|
|
22
|
+
password += alphabet[bytes[index] % alphabet.length];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return password;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createRandomSessionSecret() {
|
|
29
|
+
return randomBytes(32).toString('base64url');
|
|
30
|
+
}
|