@efa-one/sdk 1.16.1 → 1.17.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/README.md +8 -0
- package/backend/auth.d.ts +3 -0
- package/backend/auth.js +45 -7
- package/backend/index.d.ts +1 -0
- package/backend/index.js +1 -0
- package/backend/sessionStatusClient.d.ts +14 -0
- package/backend/sessionStatusClient.js +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,6 +144,14 @@ run under it. Dependabot is configured to never propose a TypeScript major.
|
|
|
144
144
|
|
|
145
145
|
SemVer. Consuming apps pull new versions with `npm update @efa-one/sdk`.
|
|
146
146
|
|
|
147
|
+
**Kernel compatibility — 1.17.0 needs an up-to-date kernel.** Since 1.17.0 the
|
|
148
|
+
`app_session` cookie is bound to the kernel session and `requireAuth` checks it
|
|
149
|
+
live via the kernel endpoint `GET /api/internal/sessions/:jti/status`. Against a
|
|
150
|
+
kernel without that endpoint, every authenticated request fails closed with
|
|
151
|
+
`503 Session service unavailable`. Roll out the kernel first, then apps on 1.17.
|
|
152
|
+
Sessions issued by an older SDK are rejected once (401) and renewed on the next
|
|
153
|
+
iframe load.
|
|
154
|
+
|
|
147
155
|
## License
|
|
148
156
|
|
|
149
157
|
[Apache-2.0](./LICENSE) — permissive with a patent grant, so customers and partners
|
package/backend/auth.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface JwtPayload {
|
|
|
9
9
|
iat: number;
|
|
10
10
|
exp: number;
|
|
11
11
|
forcePasswordChange?: boolean;
|
|
12
|
+
jti?: string;
|
|
12
13
|
}
|
|
13
14
|
export interface SessionPayload {
|
|
14
15
|
sub: string;
|
|
@@ -17,6 +18,8 @@ export interface SessionPayload {
|
|
|
17
18
|
email: string | null;
|
|
18
19
|
language?: string;
|
|
19
20
|
tenant: string;
|
|
21
|
+
kernelJti?: string;
|
|
22
|
+
kernelIat?: number;
|
|
20
23
|
}
|
|
21
24
|
export interface AuthRequest extends Request {
|
|
22
25
|
user?: SessionPayload;
|
package/backend/auth.js
CHANGED
|
@@ -14,8 +14,11 @@ exports.createExchangeRouter = createExchangeRouter;
|
|
|
14
14
|
const express_1 = require("express");
|
|
15
15
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
16
16
|
const permissionClient_1 = require("./permissionClient");
|
|
17
|
+
const sessionStatusClient_1 = require("./sessionStatusClient");
|
|
17
18
|
const JWT_ISSUER = 'converge';
|
|
18
19
|
const JWT_AUDIENCE = 'converge';
|
|
20
|
+
const KERNEL_JTI_RE = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
21
|
+
const KERNEL_SUB_RE = /^[A-Za-z0-9._:@-]{1,128}$/;
|
|
19
22
|
function getPlatformPublicKey() {
|
|
20
23
|
const s = process.env.JWT_PUBLIC_KEY;
|
|
21
24
|
if (!s)
|
|
@@ -31,14 +34,14 @@ function getSessionSecret() {
|
|
|
31
34
|
function getCookieName() {
|
|
32
35
|
return process.env.APP_SESSION_COOKIE_NAME || 'app_session';
|
|
33
36
|
}
|
|
34
|
-
function setSessionCookie(res, token) {
|
|
37
|
+
function setSessionCookie(res, token, maxAgeMs) {
|
|
35
38
|
const secure = process.env.COOKIE_SECURE
|
|
36
39
|
? ['1', 'true', 'yes', 'on'].includes(process.env.COOKIE_SECURE.toLowerCase())
|
|
37
40
|
: process.env.NODE_ENV === 'production';
|
|
38
41
|
res.cookie(getCookieName(), token, {
|
|
39
42
|
httpOnly: true,
|
|
40
43
|
sameSite: 'lax',
|
|
41
|
-
maxAge:
|
|
44
|
+
maxAge: maxAgeMs,
|
|
42
45
|
secure,
|
|
43
46
|
path: '/',
|
|
44
47
|
});
|
|
@@ -49,14 +52,36 @@ function requireAuth(req, res, next) {
|
|
|
49
52
|
res.status(401).json({ error: 'Not authenticated' });
|
|
50
53
|
return;
|
|
51
54
|
}
|
|
55
|
+
let payload;
|
|
52
56
|
try {
|
|
53
|
-
|
|
54
|
-
req.user = payload;
|
|
55
|
-
next();
|
|
57
|
+
payload = jsonwebtoken_1.default.verify(token, getSessionSecret(), { algorithms: ['HS256'] });
|
|
56
58
|
}
|
|
57
59
|
catch {
|
|
58
60
|
res.status(401).json({ error: 'Session invalid or expired' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const { kernelJti, kernelIat, exp } = payload;
|
|
64
|
+
if (typeof kernelJti !== 'string' || !kernelJti || typeof kernelIat !== 'number' || typeof exp !== 'number') {
|
|
65
|
+
res.status(401).json({ error: 'Session invalid or expired' });
|
|
66
|
+
return;
|
|
59
67
|
}
|
|
68
|
+
const verified = payload;
|
|
69
|
+
(0, sessionStatusClient_1.getSessionStatus)({
|
|
70
|
+
convergeId: verified.convergeId,
|
|
71
|
+
jti: kernelJti,
|
|
72
|
+
iat: kernelIat,
|
|
73
|
+
exp,
|
|
74
|
+
}).then((status) => {
|
|
75
|
+
if (!status.active) {
|
|
76
|
+
res.status(401).json({ error: 'Session revoked' });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
req.user = verified;
|
|
80
|
+
next();
|
|
81
|
+
}, (err) => {
|
|
82
|
+
console.error(JSON.stringify({ level: 'error', msg: 'requireAuth: session status lookup failed', err: String(err) }));
|
|
83
|
+
res.status(503).json({ error: 'Session service unavailable' });
|
|
84
|
+
});
|
|
60
85
|
}
|
|
61
86
|
function requireAdmin(req, res, next) {
|
|
62
87
|
requireAuth(req, res, () => {
|
|
@@ -177,6 +202,17 @@ function createExchangeRouter(pool) {
|
|
|
177
202
|
res.status(401).json({ error: 'Invalid or expired platform token' });
|
|
178
203
|
return;
|
|
179
204
|
}
|
|
205
|
+
if (typeof convergePayload.jti !== 'string' || !KERNEL_JTI_RE.test(convergePayload.jti) ||
|
|
206
|
+
typeof convergePayload.sub !== 'string' || !KERNEL_SUB_RE.test(convergePayload.sub) ||
|
|
207
|
+
!Number.isInteger(convergePayload.iat) || !Number.isInteger(convergePayload.exp)) {
|
|
208
|
+
res.status(401).json({ error: 'Invalid or expired platform token' });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const remainingSec = convergePayload.exp - Math.floor(Date.now() / 1000);
|
|
212
|
+
if (remainingSec <= 0) {
|
|
213
|
+
res.status(401).json({ error: 'Invalid or expired platform token' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
180
216
|
try {
|
|
181
217
|
const { rows } = await pool.query(`INSERT INTO app_users (converge_id, email, name, last_seen_at)
|
|
182
218
|
VALUES ($1, $2, $3, NOW())
|
|
@@ -197,9 +233,11 @@ function createExchangeRouter(pool) {
|
|
|
197
233
|
email: convergePayload.email ?? null,
|
|
198
234
|
language: convergePayload.language,
|
|
199
235
|
tenant: convergePayload.tenant,
|
|
236
|
+
kernelJti: convergePayload.jti,
|
|
237
|
+
kernelIat: convergePayload.iat,
|
|
200
238
|
};
|
|
201
|
-
const sessionToken = jsonwebtoken_1.default.sign(sessionPayload, getSessionSecret(), { algorithm: 'HS256', expiresIn:
|
|
202
|
-
setSessionCookie(res, sessionToken);
|
|
239
|
+
const sessionToken = jsonwebtoken_1.default.sign(sessionPayload, getSessionSecret(), { algorithm: 'HS256', expiresIn: remainingSec });
|
|
240
|
+
setSessionCookie(res, sessionToken, remainingSec * 1000);
|
|
203
241
|
res.json({
|
|
204
242
|
user: {
|
|
205
243
|
id: appUser.id,
|
package/backend/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './mail';
|
|
|
6
6
|
export * from './notifications';
|
|
7
7
|
export * from './permissions';
|
|
8
8
|
export * from './permissionClient';
|
|
9
|
+
export * from './sessionStatusClient';
|
|
9
10
|
export * from './permissionCheck';
|
|
10
11
|
export * from './customPermissions';
|
|
11
12
|
export * from './serviceClient';
|
package/backend/index.js
CHANGED
|
@@ -22,6 +22,7 @@ __exportStar(require("./mail"), exports);
|
|
|
22
22
|
__exportStar(require("./notifications"), exports);
|
|
23
23
|
__exportStar(require("./permissions"), exports);
|
|
24
24
|
__exportStar(require("./permissionClient"), exports);
|
|
25
|
+
__exportStar(require("./sessionStatusClient"), exports);
|
|
25
26
|
__exportStar(require("./permissionCheck"), exports);
|
|
26
27
|
__exportStar(require("./customPermissions"), exports);
|
|
27
28
|
__exportStar(require("./serviceClient"), exports);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SessionInactiveReason = 'server_restarted' | 'session_revoked';
|
|
2
|
+
export interface SessionStatus {
|
|
3
|
+
active: boolean;
|
|
4
|
+
reason?: SessionInactiveReason;
|
|
5
|
+
}
|
|
6
|
+
export interface SessionRef {
|
|
7
|
+
convergeId: string;
|
|
8
|
+
jti: string;
|
|
9
|
+
iat: number;
|
|
10
|
+
exp: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function getSessionStatus(ref: SessionRef): Promise<SessionStatus>;
|
|
13
|
+
export declare function clearSessionStatusCache(): void;
|
|
14
|
+
export declare function sessionStatusCacheSize(): number;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efa-one/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Offizielles SDK für efa-one-Apps: Auth/Session, Service-Discovery, Gateway-Client, Audit/Reporting/Mail/Notifications, Permission- und Capability-Registrierung sowie das Frontend-IPC-/i18n-Toolkit.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|