@cloudron/tegel 1.1.4 → 1.1.6
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/index.js +35 -4
- package/package.json +2 -2
- package/src/oidc.js +24 -10
package/index.js
CHANGED
|
@@ -3,8 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import session from 'express-session';
|
|
5
5
|
import FileStoreFactory from 'session-file-store';
|
|
6
|
-
import lastMile from '@cloudron/connect-lastmile';
|
|
7
|
-
import { HttpError } from '@cloudron/connect-lastmile';
|
|
6
|
+
import { lastMile, HttpError } from '@cloudron/connect-lastmile';
|
|
8
7
|
import * as oidc from './src/oidc.js';
|
|
9
8
|
|
|
10
9
|
export async function createExpressApp({ oidcConfig, jsonBodySizeLimit = '25mb' }) {
|
|
@@ -133,10 +132,35 @@ export function logout(redirectTo) {
|
|
|
133
132
|
};
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
export function requireAuth(redirectTo = '') {
|
|
135
|
+
export function requireAuth(redirectTo = '', { requiredScopes = [] } = {}) {
|
|
137
136
|
if (typeof redirectTo !== 'string') throw new Error('requireAuth needs a redirectTo path as non-empty string');
|
|
138
137
|
|
|
139
|
-
return (req, res, next) => {
|
|
138
|
+
return async (req, res, next) => {
|
|
139
|
+
const accessToken = extractAccessToken(req);
|
|
140
|
+
|
|
141
|
+
if (accessToken) {
|
|
142
|
+
try {
|
|
143
|
+
const introspection = await oidc.introspectToken(accessToken);
|
|
144
|
+
|
|
145
|
+
if (!introspection.active) return next(new HttpError(401, 'Token is not active'));
|
|
146
|
+
|
|
147
|
+
if (requiredScopes.length > 0) {
|
|
148
|
+
const grantedScopes = (introspection.scope || '').split(' ');
|
|
149
|
+
const missingScopes = requiredScopes.filter(s => !grantedScopes.includes(s));
|
|
150
|
+
if (missingScopes.length > 0) return next(new HttpError(403, `Missing required scopes: ${missingScopes.join(', ')}`));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
req.user = {
|
|
154
|
+
...introspection,
|
|
155
|
+
username: introspection.sub || introspection.username,
|
|
156
|
+
};
|
|
157
|
+
return next();
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error('Token introspection error:', error);
|
|
160
|
+
return next(new HttpError(401, 'Token introspection failed'));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
if (req.session && req.session.user) {
|
|
141
165
|
req.user = req.session.user;
|
|
142
166
|
return next();
|
|
@@ -147,3 +171,10 @@ export function requireAuth(redirectTo = '') {
|
|
|
147
171
|
res.redirect(redirectTo);
|
|
148
172
|
};
|
|
149
173
|
}
|
|
174
|
+
|
|
175
|
+
function extractAccessToken(req) {
|
|
176
|
+
const authHeader = req.get('Authorization');
|
|
177
|
+
if (authHeader && authHeader.startsWith('Bearer ')) return authHeader.slice(7);
|
|
178
|
+
if (req.query && req.query.accessToken) return req.query.accessToken;
|
|
179
|
+
return null;
|
|
180
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/tegel",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "GPL-2.0",
|
|
6
6
|
"author": "Cloudron Developers",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"lint:fix": "eslint . --fix"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@cloudron/connect-lastmile": "^
|
|
15
|
+
"@cloudron/connect-lastmile": "^3.0.0",
|
|
16
16
|
"express": "^5.2.1",
|
|
17
17
|
"express-session": "^1.19.0",
|
|
18
18
|
"openid-client": "^6.8.2",
|
package/src/oidc.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as client from 'openid-client';
|
|
|
2
2
|
|
|
3
3
|
let clientConfig = null;
|
|
4
4
|
let redirectUri = null;
|
|
5
|
+
let clientScope = 'openid profile email';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Initialize the OIDC client by discovering the issuer
|
|
@@ -10,11 +11,14 @@ export async function initOIDC({
|
|
|
10
11
|
issuer = process.env.CLOUDRON_OIDC_ISSUER,
|
|
11
12
|
clientId = process.env.CLOUDRON_OIDC_CLIENT_ID,
|
|
12
13
|
clientSecret = process.env.CLOUDRON_OIDC_CLIENT_SECRET,
|
|
13
|
-
callbackUrl = (process.env.CLOUDRON_APP_ORIGIN || 'http://localhost:3000') + '/auth/callback'
|
|
14
|
+
callbackUrl = (process.env.CLOUDRON_APP_ORIGIN || 'http://localhost:3000') + '/auth/callback',
|
|
15
|
+
extraScope = '',
|
|
14
16
|
}) {
|
|
15
17
|
const issuerUrl = new URL(issuer);
|
|
16
18
|
redirectUri = new URL(callbackUrl);
|
|
17
19
|
|
|
20
|
+
clientScope = `${clientScope} ${extraScope}`;
|
|
21
|
+
|
|
18
22
|
clientConfig = await client.discovery(
|
|
19
23
|
issuerUrl,
|
|
20
24
|
clientId,
|
|
@@ -40,7 +44,7 @@ export async function getAuthorizationUrl(req) {
|
|
|
40
44
|
|
|
41
45
|
const parameters = {
|
|
42
46
|
redirect_uri: redirectUri,
|
|
43
|
-
scope:
|
|
47
|
+
scope: clientScope,
|
|
44
48
|
code_challenge: codeChallenge,
|
|
45
49
|
code_challenge_method: 'S256',
|
|
46
50
|
state
|
|
@@ -84,18 +88,28 @@ export async function handleCallback(req) {
|
|
|
84
88
|
);
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
console.log(tokens)
|
|
92
|
+
|
|
87
93
|
// Clean up session OIDC state
|
|
88
94
|
delete req.session.oidc;
|
|
89
95
|
|
|
96
|
+
// give common props a nicer name but otherwise return full userInfo as it may have other scopes
|
|
97
|
+
userInfo.username = userInfo.sub;
|
|
98
|
+
userInfo.username = userInfo.sub;
|
|
99
|
+
userInfo.familyName = userInfo.family_name;
|
|
100
|
+
userInfo.givenName = userInfo.given_name;
|
|
101
|
+
userInfo.displayName = userInfo.name;
|
|
102
|
+
|
|
90
103
|
return {
|
|
91
104
|
tokens,
|
|
92
|
-
user:
|
|
93
|
-
username: userInfo.sub,
|
|
94
|
-
email: userInfo.email,
|
|
95
|
-
familyName: userInfo.family_name,
|
|
96
|
-
givenName: userInfo.given_name,
|
|
97
|
-
displayName: userInfo.name,
|
|
98
|
-
picture: userInfo.picture
|
|
99
|
-
}
|
|
105
|
+
user: userInfo,
|
|
100
106
|
};
|
|
101
107
|
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Introspect an access token via the OIDC provider's token introspection endpoint (RFC 7662)
|
|
111
|
+
*/
|
|
112
|
+
export async function introspectToken(token) {
|
|
113
|
+
if (!clientConfig) throw new Error('call initOIDC() first');
|
|
114
|
+
return client.tokenIntrospection(clientConfig, token);
|
|
115
|
+
}
|