@backstage/plugin-auth-backend 0.16.0 → 0.17.0-next.1
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/CHANGELOG.md +41 -0
- package/dist/index.cjs.js +76 -34
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +13 -5
- package/package.json +11 -12
- package/LICENSE +0 -201
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @backstage/plugin-auth-backend
|
|
2
2
|
|
|
3
|
+
## 0.17.0-next.1
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers.
|
|
12
|
+
- Updated dependencies
|
|
13
|
+
- @backstage/catalog-client@1.1.1-next.1
|
|
14
|
+
- @backstage/backend-common@0.15.2-next.1
|
|
15
|
+
- @backstage/catalog-model@1.1.2-next.1
|
|
16
|
+
- @backstage/config@1.0.3-next.1
|
|
17
|
+
- @backstage/errors@1.1.2-next.1
|
|
18
|
+
- @backstage/types@1.0.0
|
|
19
|
+
- @backstage/plugin-auth-node@0.2.6-next.1
|
|
20
|
+
|
|
21
|
+
## 0.17.0-next.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute.
|
|
26
|
+
CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute.
|
|
27
|
+
defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests.
|
|
28
|
+
|
|
29
|
+
OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`.
|
|
30
|
+
OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set.
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL.
|
|
35
|
+
- Updated dependencies
|
|
36
|
+
- @backstage/catalog-model@1.1.2-next.0
|
|
37
|
+
- @backstage/catalog-client@1.1.1-next.0
|
|
38
|
+
- @backstage/backend-common@0.15.2-next.0
|
|
39
|
+
- @backstage/plugin-auth-node@0.2.6-next.0
|
|
40
|
+
- @backstage/config@1.0.3-next.0
|
|
41
|
+
- @backstage/errors@1.1.2-next.0
|
|
42
|
+
- @backstage/types@1.0.0
|
|
43
|
+
|
|
3
44
|
## 0.16.0
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
package/dist/index.cjs.js
CHANGED
|
@@ -165,12 +165,17 @@ const verifyNonce = (req, providerId) => {
|
|
|
165
165
|
};
|
|
166
166
|
const defaultCookieConfigurer = ({
|
|
167
167
|
callbackUrl,
|
|
168
|
-
providerId
|
|
168
|
+
providerId,
|
|
169
|
+
appOrigin
|
|
169
170
|
}) => {
|
|
170
171
|
const { hostname: domain, pathname, protocol } = new URL(callbackUrl);
|
|
171
172
|
const secure = protocol === "https:";
|
|
173
|
+
let sameSite = "lax";
|
|
174
|
+
if (new URL(appOrigin).hostname !== domain && secure) {
|
|
175
|
+
sameSite = "none";
|
|
176
|
+
}
|
|
172
177
|
const path = pathname.endsWith(`${providerId}/handler/frame`) ? pathname.slice(0, -"/handler/frame".length) : `${pathname}/${providerId}`;
|
|
173
|
-
return { domain, path, secure };
|
|
178
|
+
return { domain, path, secure, sameSite };
|
|
174
179
|
};
|
|
175
180
|
|
|
176
181
|
class OAuthEnvironmentHandler {
|
|
@@ -286,58 +291,65 @@ class OAuthAdapter {
|
|
|
286
291
|
constructor(handlers, options) {
|
|
287
292
|
this.handlers = handlers;
|
|
288
293
|
this.options = options;
|
|
289
|
-
this.setNonceCookie = (res, nonce) => {
|
|
294
|
+
this.setNonceCookie = (res, nonce, cookieConfig) => {
|
|
290
295
|
res.cookie(`${this.options.providerId}-nonce`, nonce, {
|
|
291
296
|
maxAge: TEN_MINUTES_MS,
|
|
292
297
|
...this.baseCookieOptions,
|
|
293
|
-
|
|
298
|
+
...cookieConfig,
|
|
299
|
+
path: `${cookieConfig.path}/handler`
|
|
294
300
|
});
|
|
295
301
|
};
|
|
296
|
-
this.setGrantedScopeCookie = (res, scope) => {
|
|
302
|
+
this.setGrantedScopeCookie = (res, scope, cookieConfig) => {
|
|
297
303
|
res.cookie(`${this.options.providerId}-granted-scope`, scope, {
|
|
298
304
|
maxAge: THOUSAND_DAYS_MS,
|
|
299
|
-
...this.baseCookieOptions
|
|
305
|
+
...this.baseCookieOptions,
|
|
306
|
+
...cookieConfig
|
|
300
307
|
});
|
|
301
308
|
};
|
|
309
|
+
this.getRefreshTokenFromCookie = (req) => {
|
|
310
|
+
return req.cookies[`${this.options.providerId}-refresh-token`];
|
|
311
|
+
};
|
|
302
312
|
this.getGrantedScopeFromCookie = (req) => {
|
|
303
313
|
return req.cookies[`${this.options.providerId}-granted-scope`];
|
|
304
314
|
};
|
|
305
|
-
this.setRefreshTokenCookie = (res, refreshToken) => {
|
|
315
|
+
this.setRefreshTokenCookie = (res, refreshToken, cookieConfig) => {
|
|
306
316
|
res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, {
|
|
307
317
|
maxAge: THOUSAND_DAYS_MS,
|
|
308
|
-
...this.baseCookieOptions
|
|
318
|
+
...this.baseCookieOptions,
|
|
319
|
+
...cookieConfig
|
|
309
320
|
});
|
|
310
321
|
};
|
|
311
|
-
this.removeRefreshTokenCookie = (res) => {
|
|
322
|
+
this.removeRefreshTokenCookie = (res, cookieConfig) => {
|
|
312
323
|
res.cookie(`${this.options.providerId}-refresh-token`, "", {
|
|
313
324
|
maxAge: 0,
|
|
314
|
-
...this.baseCookieOptions
|
|
325
|
+
...this.baseCookieOptions,
|
|
326
|
+
...cookieConfig
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
this.getCookieConfig = (origin) => {
|
|
330
|
+
return this.options.cookieConfigurer({
|
|
331
|
+
providerId: this.options.providerId,
|
|
332
|
+
baseUrl: this.options.baseUrl,
|
|
333
|
+
callbackUrl: this.options.callbackUrl,
|
|
334
|
+
appOrigin: origin != null ? origin : this.options.appOrigin
|
|
315
335
|
});
|
|
316
336
|
};
|
|
317
337
|
this.baseCookieOptions = {
|
|
318
338
|
httpOnly: true,
|
|
319
|
-
sameSite: "lax"
|
|
320
|
-
secure: this.options.secure,
|
|
321
|
-
path: this.options.cookiePath,
|
|
322
|
-
domain: this.options.cookieDomain
|
|
339
|
+
sameSite: "lax"
|
|
323
340
|
};
|
|
324
341
|
}
|
|
325
342
|
static fromConfig(config, handlers, options) {
|
|
326
343
|
var _a;
|
|
327
|
-
const {
|
|
344
|
+
const { appUrl, baseUrl, isOriginAllowed } = config;
|
|
345
|
+
const { origin: appOrigin } = new url.URL(appUrl);
|
|
328
346
|
const cookieConfigurer = (_a = config.cookieConfigurer) != null ? _a : defaultCookieConfigurer;
|
|
329
|
-
const cookieConfig = cookieConfigurer({
|
|
330
|
-
providerId: options.providerId,
|
|
331
|
-
baseUrl: config.baseUrl,
|
|
332
|
-
callbackUrl: options.callbackUrl
|
|
333
|
-
});
|
|
334
347
|
return new OAuthAdapter(handlers, {
|
|
335
348
|
...options,
|
|
336
349
|
appOrigin,
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
isOriginAllowed: config.isOriginAllowed
|
|
350
|
+
baseUrl,
|
|
351
|
+
cookieConfigurer,
|
|
352
|
+
isOriginAllowed
|
|
341
353
|
});
|
|
342
354
|
}
|
|
343
355
|
async start(req, res) {
|
|
@@ -348,8 +360,9 @@ class OAuthAdapter {
|
|
|
348
360
|
if (!env) {
|
|
349
361
|
throw new errors.InputError("No env provided in request query parameters");
|
|
350
362
|
}
|
|
363
|
+
const cookieConfig = this.getCookieConfig(origin);
|
|
351
364
|
const nonce = crypto__default["default"].randomBytes(16).toString("base64");
|
|
352
|
-
this.setNonceCookie(res, nonce);
|
|
365
|
+
this.setNonceCookie(res, nonce, cookieConfig);
|
|
353
366
|
const state = { nonce, env, origin };
|
|
354
367
|
if (this.options.persistScopes) {
|
|
355
368
|
state.scope = scope;
|
|
@@ -380,12 +393,13 @@ class OAuthAdapter {
|
|
|
380
393
|
}
|
|
381
394
|
verifyNonce(req, this.options.providerId);
|
|
382
395
|
const { response, refreshToken } = await this.handlers.handler(req);
|
|
396
|
+
const cookieConfig = this.getCookieConfig(appOrigin);
|
|
383
397
|
if (this.options.persistScopes && state.scope) {
|
|
384
|
-
this.setGrantedScopeCookie(res, state.scope);
|
|
398
|
+
this.setGrantedScopeCookie(res, state.scope, cookieConfig);
|
|
385
399
|
response.providerInfo.scope = state.scope;
|
|
386
400
|
}
|
|
387
401
|
if (refreshToken) {
|
|
388
|
-
this.setRefreshTokenCookie(res, refreshToken);
|
|
402
|
+
this.setRefreshTokenCookie(res, refreshToken, cookieConfig);
|
|
389
403
|
}
|
|
390
404
|
const identity = await this.populateIdentity(response.backstageIdentity);
|
|
391
405
|
return postMessageResponse(res, appOrigin, {
|
|
@@ -404,7 +418,16 @@ class OAuthAdapter {
|
|
|
404
418
|
if (!ensuresXRequestedWith(req)) {
|
|
405
419
|
throw new errors.AuthenticationError("Invalid X-Requested-With header");
|
|
406
420
|
}
|
|
407
|
-
this.
|
|
421
|
+
if (this.handlers.logout) {
|
|
422
|
+
const refreshToken = this.getRefreshTokenFromCookie(req);
|
|
423
|
+
const revokeRequest = Object.assign(req, {
|
|
424
|
+
refreshToken
|
|
425
|
+
});
|
|
426
|
+
await this.handlers.logout(revokeRequest);
|
|
427
|
+
}
|
|
428
|
+
const origin = req.get("origin");
|
|
429
|
+
const cookieConfig = this.getCookieConfig(origin);
|
|
430
|
+
this.removeRefreshTokenCookie(res, cookieConfig);
|
|
408
431
|
res.status(200).end();
|
|
409
432
|
}
|
|
410
433
|
async refresh(req, res) {
|
|
@@ -418,7 +441,7 @@ class OAuthAdapter {
|
|
|
418
441
|
);
|
|
419
442
|
}
|
|
420
443
|
try {
|
|
421
|
-
const refreshToken =
|
|
444
|
+
const refreshToken = this.getRefreshTokenFromCookie(req);
|
|
422
445
|
if (!refreshToken) {
|
|
423
446
|
throw new errors.InputError("Missing session cookie");
|
|
424
447
|
}
|
|
@@ -432,7 +455,9 @@ class OAuthAdapter {
|
|
|
432
455
|
response.backstageIdentity
|
|
433
456
|
);
|
|
434
457
|
if (newRefreshToken && newRefreshToken !== refreshToken) {
|
|
435
|
-
|
|
458
|
+
const origin = req.get("origin");
|
|
459
|
+
const cookieConfig = this.getCookieConfig(origin);
|
|
460
|
+
this.setRefreshTokenCookie(res, newRefreshToken, cookieConfig);
|
|
436
461
|
}
|
|
437
462
|
res.status(200).json({ ...response, backstageIdentity });
|
|
438
463
|
} catch (error) {
|
|
@@ -730,6 +755,8 @@ class Auth0AuthProvider {
|
|
|
730
755
|
this.authHandler = options.authHandler;
|
|
731
756
|
this.resolverContext = options.resolverContext;
|
|
732
757
|
this.audience = options.audience;
|
|
758
|
+
this.connection = options.connection;
|
|
759
|
+
this.connectionScope = options.connectionScope;
|
|
733
760
|
this._strategy = new Auth0Strategy(
|
|
734
761
|
{
|
|
735
762
|
clientID: options.clientId,
|
|
@@ -761,12 +788,16 @@ class Auth0AuthProvider {
|
|
|
761
788
|
prompt: "consent",
|
|
762
789
|
scope: req.scope,
|
|
763
790
|
state: encodeState(req.state),
|
|
764
|
-
...this.audience ? { audience: this.audience } : {}
|
|
791
|
+
...this.audience ? { audience: this.audience } : {},
|
|
792
|
+
...this.connection ? { connection: this.connection } : {},
|
|
793
|
+
...this.connectionScope ? { connection_scope: this.connectionScope } : {}
|
|
765
794
|
});
|
|
766
795
|
}
|
|
767
796
|
async handler(req) {
|
|
768
797
|
const { result, privateInfo } = await executeFrameHandlerStrategy(req, this._strategy, {
|
|
769
|
-
...this.audience ? { audience: this.audience } : {}
|
|
798
|
+
...this.audience ? { audience: this.audience } : {},
|
|
799
|
+
...this.connection ? { connection: this.connection } : {},
|
|
800
|
+
...this.connectionScope ? { connection_scope: this.connectionScope } : {}
|
|
770
801
|
});
|
|
771
802
|
return {
|
|
772
803
|
response: await this.handleResult(result),
|
|
@@ -824,6 +855,8 @@ const auth0 = createAuthProviderIntegration({
|
|
|
824
855
|
const domain = envConfig.getString("domain");
|
|
825
856
|
const customCallbackUrl = envConfig.getOptionalString("callbackUrl");
|
|
826
857
|
const audience = envConfig.getOptionalString("audience");
|
|
858
|
+
const connection = envConfig.getOptionalString("connection");
|
|
859
|
+
const connectionScope = envConfig.getOptionalString("connectionScope");
|
|
827
860
|
const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`;
|
|
828
861
|
const authHandler = (options == null ? void 0 : options.authHandler) ? options.authHandler : async ({ fullProfile, params }) => ({
|
|
829
862
|
profile: makeProfileInfo(fullProfile, params.id_token)
|
|
@@ -837,7 +870,9 @@ const auth0 = createAuthProviderIntegration({
|
|
|
837
870
|
authHandler,
|
|
838
871
|
signInResolver,
|
|
839
872
|
resolverContext,
|
|
840
|
-
audience
|
|
873
|
+
audience,
|
|
874
|
+
connection,
|
|
875
|
+
connectionScope
|
|
841
876
|
});
|
|
842
877
|
return OAuthAdapter.fromConfig(globalConfig, provider, {
|
|
843
878
|
providerId,
|
|
@@ -1555,7 +1590,10 @@ class GitlabAuthProvider {
|
|
|
1555
1590
|
clientID: options.clientId,
|
|
1556
1591
|
clientSecret: options.clientSecret,
|
|
1557
1592
|
callbackURL: options.callbackUrl,
|
|
1558
|
-
baseURL: options.baseUrl
|
|
1593
|
+
baseURL: options.baseUrl,
|
|
1594
|
+
authorizationURL: `${options.baseUrl}/oauth/authorize`,
|
|
1595
|
+
tokenURL: `${options.baseUrl}/oauth/token`,
|
|
1596
|
+
profileURL: `${options.baseUrl}/api/v4/user`
|
|
1559
1597
|
},
|
|
1560
1598
|
(accessToken, refreshToken, params, fullProfile, done) => {
|
|
1561
1599
|
done(
|
|
@@ -1694,6 +1732,10 @@ class GoogleAuthProvider {
|
|
|
1694
1732
|
refreshToken: privateInfo.refreshToken
|
|
1695
1733
|
};
|
|
1696
1734
|
}
|
|
1735
|
+
async logout(req) {
|
|
1736
|
+
const oauthClient = new googleAuthLibrary.OAuth2Client();
|
|
1737
|
+
await oauthClient.revokeToken(req.refreshToken);
|
|
1738
|
+
}
|
|
1697
1739
|
async refresh(req) {
|
|
1698
1740
|
const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy(
|
|
1699
1741
|
this.strategy,
|