@backstage/backend-defaults 0.5.1-next.0 → 0.5.1-next.2
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 +46 -0
- package/auth/package.json +1 -1
- package/cache/package.json +1 -1
- package/database/package.json +1 -1
- package/discovery/package.json +1 -1
- package/dist/CreateBackend.cjs.js +49 -0
- package/dist/CreateBackend.cjs.js.map +1 -0
- package/dist/PackageDiscoveryService.cjs.js +109 -0
- package/dist/PackageDiscoveryService.cjs.js.map +1 -0
- package/dist/auth.cjs.js +2 -996
- package/dist/auth.cjs.js.map +1 -1
- package/dist/cache.cjs.js +4 -204
- package/dist/cache.cjs.js.map +1 -1
- package/dist/database.cjs.js +4 -957
- package/dist/database.cjs.js.map +1 -1
- package/dist/database.d.ts +4 -1
- package/dist/discovery.cjs.js +4 -92
- package/dist/discovery.cjs.js.map +1 -1
- package/dist/discoveryFeatureLoader.cjs.js +19 -0
- package/dist/discoveryFeatureLoader.cjs.js.map +1 -0
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js +130 -0
- package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -0
- package/dist/entrypoints/auth/JwksClient.cjs.js +49 -0
- package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -0
- package/dist/entrypoints/auth/authServiceFactory.cjs.js +57 -0
- package/dist/entrypoints/auth/authServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js +78 -0
- package/dist/entrypoints/auth/external/ExternalTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/helpers.cjs.js +92 -0
- package/dist/entrypoints/auth/external/helpers.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/jwks.cjs.js +63 -0
- package/dist/entrypoints/auth/external/jwks.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/legacy.cjs.js +73 -0
- package/dist/entrypoints/auth/external/legacy.cjs.js.map +1 -0
- package/dist/entrypoints/auth/external/static.cjs.js +33 -0
- package/dist/entrypoints/auth/external/static.cjs.js.map +1 -0
- package/dist/{cjs/helpers-D2f1CG0o.cjs.js → entrypoints/auth/helpers.cjs.js} +1 -1
- package/dist/entrypoints/auth/helpers.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +147 -0
- package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js +73 -0
- package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js +75 -0
- package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js +91 -0
- package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js.map +1 -0
- package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js +29 -0
- package/dist/entrypoints/auth/plugin/keys/createPluginKeySource.cjs.js.map +1 -0
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +110 -0
- package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -0
- package/dist/entrypoints/cache/CacheClient.cjs.js +50 -0
- package/dist/entrypoints/cache/CacheClient.cjs.js.map +1 -0
- package/dist/entrypoints/cache/CacheManager.cjs.js +147 -0
- package/dist/entrypoints/cache/CacheManager.cjs.js.map +1 -0
- package/dist/entrypoints/cache/cacheServiceFactory.cjs.js +22 -0
- package/dist/entrypoints/cache/cacheServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/cache/types.cjs.js +10 -0
- package/dist/entrypoints/cache/types.cjs.js.map +1 -0
- package/dist/entrypoints/database/DatabaseManager.cjs.js +173 -0
- package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js +14 -0
- package/dist/entrypoints/database/connectors/defaultNameOverride.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/defaultSchemaOverride.cjs.js +12 -0
- package/dist/entrypoints/database/connectors/defaultSchemaOverride.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js +10 -0
- package/dist/entrypoints/database/connectors/mergeDatabaseConfig.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/mysql.cjs.js +278 -0
- package/dist/entrypoints/database/connectors/mysql.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/postgres.cjs.js +304 -0
- package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -0
- package/dist/entrypoints/database/connectors/sqlite3.cjs.js +251 -0
- package/dist/entrypoints/database/connectors/sqlite3.cjs.js.map +1 -0
- package/dist/entrypoints/database/databaseServiceFactory.cjs.js +36 -0
- package/dist/entrypoints/database/databaseServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js +86 -0
- package/dist/entrypoints/discovery/HostDiscovery.cjs.js.map +1 -0
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js +17 -0
- package/dist/entrypoints/discovery/discoveryServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js +192 -0
- package/dist/entrypoints/httpAuth/httpAuthServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js +19 -0
- package/dist/entrypoints/httpRouter/createAuthIntegrationRouter.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js +26 -0
- package/dist/entrypoints/httpRouter/createCookieAuthRefreshMiddleware.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js +63 -0
- package/dist/entrypoints/httpRouter/createCredentialsBarrier.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js +52 -0
- package/dist/entrypoints/httpRouter/createLifecycleMiddleware.cjs.js.map +1 -0
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js +48 -0
- package/dist/entrypoints/httpRouter/httpRouterServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js +88 -0
- package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/logger/loggerServiceFactory.cjs.js +17 -0
- package/dist/entrypoints/logger/loggerServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js +22 -0
- package/dist/entrypoints/permissions/permissionsServiceFactory.cjs.js.map +1 -0
- package/dist/{cjs/createConfigSecretEnumerator-DShyoWWL.cjs.js → entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js} +1 -1
- package/dist/entrypoints/rootConfig/createConfigSecretEnumerator.cjs.js.map +1 -0
- package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js +26 -0
- package/dist/entrypoints/rootConfig/rootConfigServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js +41 -0
- package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js +77 -0
- package/dist/entrypoints/rootHttpRouter/DefaultRootHttpRouter.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js +29 -0
- package/dist/entrypoints/rootHttpRouter/createHealthRouter.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js +187 -0
- package/dist/entrypoints/rootHttpRouter/http/MiddlewareFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js +28 -0
- package/dist/entrypoints/rootHttpRouter/http/applyInternalErrorFilter.cjs.js.map +1 -0
- package/dist/{cjs/config-BDOwXIyo.cjs.js → entrypoints/rootHttpRouter/http/config.cjs.js} +1 -1
- package/dist/entrypoints/rootHttpRouter/http/config.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js +88 -0
- package/dist/entrypoints/rootHttpRouter/http/createHttpServer.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js +130 -0
- package/dist/entrypoints/rootHttpRouter/http/getGeneratedCertificate.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js +51 -0
- package/dist/entrypoints/rootHttpRouter/http/readCorsOptions.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js +62 -0
- package/dist/entrypoints/rootHttpRouter/http/readHelmetOptions.cjs.js.map +1 -0
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js +73 -0
- package/dist/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js +76 -0
- package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js +114 -0
- package/dist/entrypoints/rootLogger/WinstonLogger.cjs.js.map +1 -0
- package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js +30 -0
- package/dist/entrypoints/rootLogger/rootLoggerServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js +18 -0
- package/dist/entrypoints/scheduler/database/migrateBackendTasks.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/database/tables.cjs.js +8 -0
- package/dist/entrypoints/scheduler/database/tables.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js +37 -0
- package/dist/entrypoints/scheduler/lib/DefaultSchedulerService.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js +105 -0
- package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +138 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js +59 -0
- package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js +275 -0
- package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/types.cjs.js +60 -0
- package/dist/entrypoints/scheduler/lib/types.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/lib/util.cjs.js +66 -0
- package/dist/entrypoints/scheduler/lib/util.cjs.js.map +1 -0
- package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js +19 -0
- package/dist/entrypoints/scheduler/schedulerServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +274 -0
- package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +261 -0
- package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +148 -0
- package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +174 -0
- package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +170 -0
- package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +182 -0
- package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js +132 -0
- package/dist/entrypoints/urlReader/lib/FetchUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js +147 -0
- package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +122 -0
- package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js +226 -0
- package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +277 -0
- package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +129 -0
- package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js +120 -0
- package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js +49 -0
- package/dist/entrypoints/urlReader/lib/ReadUrlResponseFactory.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js +46 -0
- package/dist/entrypoints/urlReader/lib/UrlReaderPredicateMux.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js +68 -0
- package/dist/entrypoints/urlReader/lib/UrlReaders.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +46 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js +78 -0
- package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js +147 -0
- package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js +161 -0
- package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/tree/util.cjs.js +28 -0
- package/dist/entrypoints/urlReader/lib/tree/util.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/lib/util.cjs.js +11 -0
- package/dist/entrypoints/urlReader/lib/util.cjs.js.map +1 -0
- package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js +29 -0
- package/dist/entrypoints/urlReader/urlReaderServiceFactory.cjs.js.map +1 -0
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js +59 -0
- package/dist/entrypoints/userInfo/DefaultUserInfoService.cjs.js.map +1 -0
- package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js +17 -0
- package/dist/entrypoints/userInfo/userInfoServiceFactory.cjs.js.map +1 -0
- package/dist/httpAuth.cjs.js +3 -187
- package/dist/httpAuth.cjs.js.map +1 -1
- package/dist/httpRouter.cjs.js +2 -166
- package/dist/httpRouter.cjs.js.map +1 -1
- package/dist/index.cjs.js +4 -160
- package/dist/index.cjs.js.map +1 -1
- package/dist/lib/escapeRegExp.cjs.js +8 -0
- package/dist/lib/escapeRegExp.cjs.js.map +1 -0
- package/dist/lifecycle.cjs.js +3 -58
- package/dist/lifecycle.cjs.js.map +1 -1
- package/dist/logger.cjs.js +3 -12
- package/dist/logger.cjs.js.map +1 -1
- package/dist/package.json.cjs.js +252 -0
- package/dist/package.json.cjs.js.map +1 -0
- package/dist/permissions.cjs.js +3 -17
- package/dist/permissions.cjs.js.map +1 -1
- package/dist/rootConfig.cjs.js +4 -22
- package/dist/rootConfig.cjs.js.map +1 -1
- package/dist/rootHealth.cjs.js +3 -35
- package/dist/rootHealth.cjs.js.map +1 -1
- package/dist/rootHttpRouter.cjs.js +15 -651
- package/dist/rootHttpRouter.cjs.js.map +1 -1
- package/dist/rootLifecycle.cjs.js +3 -70
- package/dist/rootLifecycle.cjs.js.map +1 -1
- package/dist/rootLogger.cjs.js +4 -137
- package/dist/rootLogger.cjs.js.map +1 -1
- package/dist/scheduler.cjs.js +4 -693
- package/dist/scheduler.cjs.js.map +1 -1
- package/dist/scheduler.d.ts +2 -1
- package/dist/urlReader.cjs.js +32 -2962
- package/dist/urlReader.cjs.js.map +1 -1
- package/dist/userInfo.cjs.js +2 -64
- package/dist/userInfo.cjs.js.map +1 -1
- package/httpAuth/package.json +1 -1
- package/httpRouter/package.json +1 -1
- package/lifecycle/package.json +1 -1
- package/logger/package.json +1 -1
- package/package.json +20 -20
- package/permissions/package.json +1 -1
- package/rootConfig/package.json +1 -1
- package/rootHealth/package.json +1 -1
- package/rootHttpRouter/package.json +1 -1
- package/rootLifecycle/package.json +1 -1
- package/rootLogger/package.json +1 -1
- package/scheduler/package.json +1 -1
- package/urlReader/package.json +1 -1
- package/userInfo/package.json +1 -1
- package/dist/cjs/config-BDOwXIyo.cjs.js.map +0 -1
- package/dist/cjs/createConfigSecretEnumerator-DShyoWWL.cjs.js.map +0 -1
- package/dist/cjs/helpers-D2f1CG0o.cjs.js.map +0 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
4
|
+
var DefaultAuthService = require('./DefaultAuthService.cjs.js');
|
|
5
|
+
var ExternalTokenHandler = require('./external/ExternalTokenHandler.cjs.js');
|
|
6
|
+
var PluginTokenHandler = require('./plugin/PluginTokenHandler.cjs.js');
|
|
7
|
+
var createPluginKeySource = require('./plugin/keys/createPluginKeySource.cjs.js');
|
|
8
|
+
var UserTokenHandler = require('./user/UserTokenHandler.cjs.js');
|
|
9
|
+
|
|
10
|
+
const authServiceFactory = backendPluginApi.createServiceFactory({
|
|
11
|
+
service: backendPluginApi.coreServices.auth,
|
|
12
|
+
deps: {
|
|
13
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
14
|
+
logger: backendPluginApi.coreServices.rootLogger,
|
|
15
|
+
discovery: backendPluginApi.coreServices.discovery,
|
|
16
|
+
plugin: backendPluginApi.coreServices.pluginMetadata,
|
|
17
|
+
database: backendPluginApi.coreServices.database
|
|
18
|
+
},
|
|
19
|
+
async factory({ config, discovery, plugin, logger, database }) {
|
|
20
|
+
const disableDefaultAuthPolicy = config.getOptionalBoolean(
|
|
21
|
+
"backend.auth.dangerouslyDisableDefaultAuthPolicy"
|
|
22
|
+
) ?? false;
|
|
23
|
+
const keyDuration = { hours: 1 };
|
|
24
|
+
const keySource = await createPluginKeySource.createPluginKeySource({
|
|
25
|
+
config,
|
|
26
|
+
database,
|
|
27
|
+
logger,
|
|
28
|
+
keyDuration
|
|
29
|
+
});
|
|
30
|
+
const userTokens = UserTokenHandler.UserTokenHandler.create({
|
|
31
|
+
discovery
|
|
32
|
+
});
|
|
33
|
+
const pluginTokens = PluginTokenHandler.PluginTokenHandler.create({
|
|
34
|
+
ownPluginId: plugin.getId(),
|
|
35
|
+
logger,
|
|
36
|
+
keySource,
|
|
37
|
+
keyDuration,
|
|
38
|
+
discovery
|
|
39
|
+
});
|
|
40
|
+
const externalTokens = ExternalTokenHandler.ExternalTokenHandler.create({
|
|
41
|
+
ownPluginId: plugin.getId(),
|
|
42
|
+
config,
|
|
43
|
+
logger
|
|
44
|
+
});
|
|
45
|
+
return new DefaultAuthService.DefaultAuthService(
|
|
46
|
+
userTokens,
|
|
47
|
+
pluginTokens,
|
|
48
|
+
externalTokens,
|
|
49
|
+
plugin.getId(),
|
|
50
|
+
disableDefaultAuthPolicy,
|
|
51
|
+
keySource
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
exports.authServiceFactory = authServiceFactory;
|
|
57
|
+
//# sourceMappingURL=authServiceFactory.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authServiceFactory.cjs.js","sources":["../../../src/entrypoints/auth/authServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\nimport { DefaultAuthService } from './DefaultAuthService';\nimport { ExternalTokenHandler } from './external/ExternalTokenHandler';\nimport { PluginTokenHandler } from './plugin/PluginTokenHandler';\nimport { createPluginKeySource } from './plugin/keys/createPluginKeySource';\nimport { UserTokenHandler } from './user/UserTokenHandler';\n\n/**\n * Handles token authentication and credentials management.\n *\n * See {@link @backstage/code-plugin-api#AuthService}\n * and {@link https://backstage.io/docs/backend-system/core-services/auth | the service docs}\n * for more information.\n *\n * @public\n */\nexport const authServiceFactory = createServiceFactory({\n service: coreServices.auth,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n discovery: coreServices.discovery,\n plugin: coreServices.pluginMetadata,\n database: coreServices.database,\n },\n async factory({ config, discovery, plugin, logger, database }) {\n const disableDefaultAuthPolicy =\n config.getOptionalBoolean(\n 'backend.auth.dangerouslyDisableDefaultAuthPolicy',\n ) ?? false;\n\n const keyDuration = { hours: 1 };\n\n const keySource = await createPluginKeySource({\n config,\n database,\n logger,\n keyDuration,\n });\n\n const userTokens = UserTokenHandler.create({\n discovery,\n });\n\n const pluginTokens = PluginTokenHandler.create({\n ownPluginId: plugin.getId(),\n logger,\n keySource,\n keyDuration,\n discovery,\n });\n\n const externalTokens = ExternalTokenHandler.create({\n ownPluginId: plugin.getId(),\n config,\n logger,\n });\n\n return new DefaultAuthService(\n userTokens,\n pluginTokens,\n externalTokens,\n plugin.getId(),\n disableDefaultAuthPolicy,\n keySource,\n );\n },\n});\n"],"names":["createServiceFactory","coreServices","createPluginKeySource","UserTokenHandler","PluginTokenHandler","ExternalTokenHandler","DefaultAuthService"],"mappings":";;;;;;;;;AAmCO,MAAM,qBAAqBA,qCAAqB,CAAA;AAAA,EACrD,SAASC,6BAAa,CAAA,IAAA;AAAA,EACtB,IAAM,EAAA;AAAA,IACJ,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,QAAQA,6BAAa,CAAA,UAAA;AAAA,IACrB,WAAWA,6BAAa,CAAA,SAAA;AAAA,IACxB,QAAQA,6BAAa,CAAA,cAAA;AAAA,IACrB,UAAUA,6BAAa,CAAA,QAAA;AAAA,GACzB;AAAA,EACA,MAAM,QAAQ,EAAE,MAAA,EAAQ,WAAW,MAAQ,EAAA,MAAA,EAAQ,UAAY,EAAA;AAC7D,IAAA,MAAM,2BACJ,MAAO,CAAA,kBAAA;AAAA,MACL,kDAAA;AAAA,KACG,IAAA,KAAA,CAAA;AAEP,IAAM,MAAA,WAAA,GAAc,EAAE,KAAA,EAAO,CAAE,EAAA,CAAA;AAE/B,IAAM,MAAA,SAAA,GAAY,MAAMC,2CAAsB,CAAA;AAAA,MAC5C,MAAA;AAAA,MACA,QAAA;AAAA,MACA,MAAA;AAAA,MACA,WAAA;AAAA,KACD,CAAA,CAAA;AAED,IAAM,MAAA,UAAA,GAAaC,kCAAiB,MAAO,CAAA;AAAA,MACzC,SAAA;AAAA,KACD,CAAA,CAAA;AAED,IAAM,MAAA,YAAA,GAAeC,sCAAmB,MAAO,CAAA;AAAA,MAC7C,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA,SAAA;AAAA,MACA,WAAA;AAAA,MACA,SAAA;AAAA,KACD,CAAA,CAAA;AAED,IAAM,MAAA,cAAA,GAAiBC,0CAAqB,MAAO,CAAA;AAAA,MACjD,WAAA,EAAa,OAAO,KAAM,EAAA;AAAA,MAC1B,MAAA;AAAA,MACA,MAAA;AAAA,KACD,CAAA,CAAA;AAED,IAAA,OAAO,IAAIC,qCAAA;AAAA,MACT,UAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAA;AAAA,MACA,OAAO,KAAM,EAAA;AAAA,MACb,wBAAA;AAAA,MACA,SAAA;AAAA,KACF,CAAA;AAAA,GACF;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@backstage/errors');
|
|
4
|
+
var legacy = require('./legacy.cjs.js');
|
|
5
|
+
var _static = require('./static.cjs.js');
|
|
6
|
+
var jwks = require('./jwks.cjs.js');
|
|
7
|
+
|
|
8
|
+
const NEW_CONFIG_KEY = "backend.auth.externalAccess";
|
|
9
|
+
const OLD_CONFIG_KEY = "backend.auth.keys";
|
|
10
|
+
let loggedDeprecationWarning = false;
|
|
11
|
+
class ExternalTokenHandler {
|
|
12
|
+
constructor(ownPluginId, handlers) {
|
|
13
|
+
this.ownPluginId = ownPluginId;
|
|
14
|
+
this.handlers = handlers;
|
|
15
|
+
}
|
|
16
|
+
static create(options) {
|
|
17
|
+
const { ownPluginId, config, logger } = options;
|
|
18
|
+
const staticHandler = new _static.StaticTokenHandler();
|
|
19
|
+
const legacyHandler = new legacy.LegacyTokenHandler();
|
|
20
|
+
const jwksHandler = new jwks.JWKSHandler();
|
|
21
|
+
const handlers = {
|
|
22
|
+
static: staticHandler,
|
|
23
|
+
legacy: legacyHandler,
|
|
24
|
+
jwks: jwksHandler
|
|
25
|
+
};
|
|
26
|
+
const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];
|
|
27
|
+
for (const handlerConfig of handlerConfigs) {
|
|
28
|
+
const type = handlerConfig.getString("type");
|
|
29
|
+
const handler = handlers[type];
|
|
30
|
+
if (!handler) {
|
|
31
|
+
const valid = Object.keys(handlers).map((k) => `'${k}'`).join(", ");
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
handler.add(handlerConfig);
|
|
37
|
+
}
|
|
38
|
+
const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];
|
|
39
|
+
if (legacyConfigs.length && !loggedDeprecationWarning) {
|
|
40
|
+
loggedDeprecationWarning = true;
|
|
41
|
+
logger.warn(
|
|
42
|
+
`DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
for (const handlerConfig of legacyConfigs) {
|
|
46
|
+
legacyHandler.addOld(handlerConfig);
|
|
47
|
+
}
|
|
48
|
+
return new ExternalTokenHandler(ownPluginId, Object.values(handlers));
|
|
49
|
+
}
|
|
50
|
+
async verifyToken(token) {
|
|
51
|
+
for (const handler of this.handlers) {
|
|
52
|
+
const result = await handler.verifyToken(token);
|
|
53
|
+
if (result) {
|
|
54
|
+
const { allAccessRestrictions, ...rest } = result;
|
|
55
|
+
if (allAccessRestrictions) {
|
|
56
|
+
const accessRestrictions = allAccessRestrictions.get(
|
|
57
|
+
this.ownPluginId
|
|
58
|
+
);
|
|
59
|
+
if (!accessRestrictions) {
|
|
60
|
+
const valid = [...allAccessRestrictions.keys()].map((k) => `'${k}'`).join(", ");
|
|
61
|
+
throw new errors.NotAllowedError(
|
|
62
|
+
`This token's access is restricted to plugin(s) ${valid}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
...rest,
|
|
67
|
+
accessRestrictions
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return rest;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return void 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.ExternalTokenHandler = ExternalTokenHandler;
|
|
78
|
+
//# sourceMappingURL=ExternalTokenHandler.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExternalTokenHandler.cjs.js","sources":["../../../../src/entrypoints/auth/external/ExternalTokenHandler.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstagePrincipalAccessRestrictions,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { NotAllowedError } from '@backstage/errors';\nimport { LegacyTokenHandler } from './legacy';\nimport { StaticTokenHandler } from './static';\nimport { JWKSHandler } from './jwks';\nimport { TokenHandler } from './types';\n\nconst NEW_CONFIG_KEY = 'backend.auth.externalAccess';\nconst OLD_CONFIG_KEY = 'backend.auth.keys';\nlet loggedDeprecationWarning = false;\n\n/**\n * Handles all types of external caller token types (i.e. not Backstage user\n * tokens, nor Backstage backend plugin tokens).\n *\n * @internal\n */\nexport class ExternalTokenHandler {\n static create(options: {\n ownPluginId: string;\n config: RootConfigService;\n logger: LoggerService;\n }): ExternalTokenHandler {\n const { ownPluginId, config, logger } = options;\n\n const staticHandler = new StaticTokenHandler();\n const legacyHandler = new LegacyTokenHandler();\n const jwksHandler = new JWKSHandler();\n const handlers: Record<string, TokenHandler> = {\n static: staticHandler,\n legacy: legacyHandler,\n jwks: jwksHandler,\n };\n\n // Load the new-style handlers\n const handlerConfigs = config.getOptionalConfigArray(NEW_CONFIG_KEY) ?? [];\n for (const handlerConfig of handlerConfigs) {\n const type = handlerConfig.getString('type');\n const handler = handlers[type];\n if (!handler) {\n const valid = Object.keys(handlers)\n .map(k => `'${k}'`)\n .join(', ');\n throw new Error(\n `Unknown type '${type}' in ${NEW_CONFIG_KEY}, expected one of ${valid}`,\n );\n }\n handler.add(handlerConfig);\n }\n\n // Load the old keys too\n const legacyConfigs = config.getOptionalConfigArray(OLD_CONFIG_KEY) ?? [];\n if (legacyConfigs.length && !loggedDeprecationWarning) {\n loggedDeprecationWarning = true;\n logger.warn(\n `DEPRECATION WARNING: The ${OLD_CONFIG_KEY} config has been replaced by ${NEW_CONFIG_KEY}, see https://backstage.io/docs/auth/service-to-service-auth`,\n );\n }\n for (const handlerConfig of legacyConfigs) {\n legacyHandler.addOld(handlerConfig);\n }\n\n return new ExternalTokenHandler(ownPluginId, Object.values(handlers));\n }\n\n constructor(\n private readonly ownPluginId: string,\n private readonly handlers: TokenHandler[],\n ) {}\n\n async verifyToken(token: string): Promise<\n | {\n subject: string;\n accessRestrictions?: BackstagePrincipalAccessRestrictions;\n }\n | undefined\n > {\n for (const handler of this.handlers) {\n const result = await handler.verifyToken(token);\n if (result) {\n const { allAccessRestrictions, ...rest } = result;\n if (allAccessRestrictions) {\n const accessRestrictions = allAccessRestrictions.get(\n this.ownPluginId,\n );\n if (!accessRestrictions) {\n const valid = [...allAccessRestrictions.keys()]\n .map(k => `'${k}'`)\n .join(', ');\n throw new NotAllowedError(\n `This token's access is restricted to plugin(s) ${valid}`,\n );\n }\n\n return {\n ...rest,\n accessRestrictions,\n };\n }\n\n return rest;\n }\n }\n\n return undefined;\n }\n}\n"],"names":["StaticTokenHandler","LegacyTokenHandler","JWKSHandler","NotAllowedError"],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function readAccessRestrictionsFromConfig(externalAccessEntryConfig) {
|
|
4
|
+
const configs = externalAccessEntryConfig.getOptionalConfigArray("accessRestrictions") ?? [];
|
|
5
|
+
const result = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const config of configs) {
|
|
7
|
+
const validKeys = ["plugin", "permission", "permissionAttribute"];
|
|
8
|
+
for (const key of config.keys()) {
|
|
9
|
+
if (!validKeys.includes(key)) {
|
|
10
|
+
const valid = validKeys.map((k) => `'${k}'`).join(", ");
|
|
11
|
+
throw new Error(
|
|
12
|
+
`Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const pluginId = config.getString("plugin");
|
|
17
|
+
const permissionNames = readPermissionNames(config);
|
|
18
|
+
const permissionAttributes = readPermissionAttributes(config);
|
|
19
|
+
if (result.has(pluginId)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
result.set(pluginId, {
|
|
25
|
+
...permissionNames ? { permissionNames } : {},
|
|
26
|
+
...permissionAttributes ? { permissionAttributes } : {}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return result.size ? result : void 0;
|
|
30
|
+
}
|
|
31
|
+
function readStringOrStringArrayFromConfig(root, key, validValues) {
|
|
32
|
+
if (!root.has(key)) {
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
const rawValues = Array.isArray(root.get(key)) ? root.getStringArray(key) : [root.getString(key)];
|
|
36
|
+
const values = [
|
|
37
|
+
...new Set(
|
|
38
|
+
rawValues.map((v) => v.split(/[ ,]/)).flat().filter(Boolean)
|
|
39
|
+
)
|
|
40
|
+
];
|
|
41
|
+
if (!values.length) {
|
|
42
|
+
return void 0;
|
|
43
|
+
}
|
|
44
|
+
if (validValues?.length) {
|
|
45
|
+
for (const value of values) {
|
|
46
|
+
if (!validValues.includes(value)) {
|
|
47
|
+
const valid = validValues.map((k) => `'${k}'`).join(", ");
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return values;
|
|
55
|
+
}
|
|
56
|
+
function readPermissionNames(externalAccessEntryConfig) {
|
|
57
|
+
return readStringOrStringArrayFromConfig(
|
|
58
|
+
externalAccessEntryConfig,
|
|
59
|
+
"permission"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function readPermissionAttributes(externalAccessEntryConfig) {
|
|
63
|
+
const config = externalAccessEntryConfig.getOptionalConfig(
|
|
64
|
+
"permissionAttribute"
|
|
65
|
+
);
|
|
66
|
+
if (!config) {
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
const validKeys = ["action"];
|
|
70
|
+
for (const key of config.keys()) {
|
|
71
|
+
if (!validKeys.includes(key)) {
|
|
72
|
+
const valid = validKeys.map((k) => `'${k}'`).join(", ");
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const action = readStringOrStringArrayFromConfig(config, "action", [
|
|
79
|
+
"create",
|
|
80
|
+
"read",
|
|
81
|
+
"update",
|
|
82
|
+
"delete"
|
|
83
|
+
]);
|
|
84
|
+
const result = {
|
|
85
|
+
...action ? { action } : {}
|
|
86
|
+
};
|
|
87
|
+
return Object.keys(result).length ? result : void 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
exports.readAccessRestrictionsFromConfig = readAccessRestrictionsFromConfig;
|
|
91
|
+
exports.readStringOrStringArrayFromConfig = readStringOrStringArrayFromConfig;
|
|
92
|
+
//# sourceMappingURL=helpers.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../../src/entrypoints/auth/external/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { AccessRestriptionsMap } from './types';\n\n/**\n * Parses and returns the `accessRestrictions` configuration from an\n * `externalAccess` entry, or undefined if there wasn't one.\n *\n * @internal\n */\nexport function readAccessRestrictionsFromConfig(\n externalAccessEntryConfig: Config,\n): AccessRestriptionsMap | undefined {\n const configs =\n externalAccessEntryConfig.getOptionalConfigArray('accessRestrictions') ??\n [];\n\n const result: AccessRestriptionsMap = new Map();\n for (const config of configs) {\n const validKeys = ['plugin', 'permission', 'permissionAttribute'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'accessRestrictions' config, expected one of ${valid}`,\n );\n }\n }\n\n const pluginId = config.getString('plugin');\n const permissionNames = readPermissionNames(config);\n const permissionAttributes = readPermissionAttributes(config);\n\n if (result.has(pluginId)) {\n throw new Error(\n `Attempted to declare 'accessRestrictions' twice for plugin '${pluginId}', which is not permitted`,\n );\n }\n\n result.set(pluginId, {\n ...(permissionNames ? { permissionNames } : {}),\n ...(permissionAttributes ? { permissionAttributes } : {}),\n });\n }\n\n return result.size ? result : undefined;\n}\n\n/**\n * Reads a config value as a string or an array of strings, and deduplicates and\n * splits by comma/space into a string array. Can also validate against a known\n * set of values. Returns undefined if the key didn't exist or if the array\n * would have ended up being empty.\n *\n * @internal\n */\nexport function readStringOrStringArrayFromConfig<T extends string>(\n root: Config,\n key: string,\n validValues?: readonly T[],\n): T[] | undefined {\n if (!root.has(key)) {\n return undefined;\n }\n\n const rawValues = Array.isArray(root.get(key))\n ? root.getStringArray(key)\n : [root.getString(key)];\n\n const values = [\n ...new Set(\n rawValues\n .map(v => v.split(/[ ,]/))\n .flat()\n .filter(Boolean),\n ),\n ];\n\n if (!values.length) {\n return undefined;\n }\n\n if (validValues?.length) {\n for (const value of values) {\n if (!validValues.includes(value as T)) {\n const valid = validValues.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid value '${value}' at '${key}' in 'permissionAttributes' config, valid values are ${valid}`,\n );\n }\n }\n }\n\n return values as T[];\n}\n\nfunction readPermissionNames(externalAccessEntryConfig: Config) {\n return readStringOrStringArrayFromConfig(\n externalAccessEntryConfig,\n 'permission',\n );\n}\n\nfunction readPermissionAttributes(externalAccessEntryConfig: Config) {\n const config = externalAccessEntryConfig.getOptionalConfig(\n 'permissionAttribute',\n );\n if (!config) {\n return undefined;\n }\n\n const validKeys = ['action'];\n for (const key of config.keys()) {\n if (!validKeys.includes(key)) {\n const valid = validKeys.map(k => `'${k}'`).join(', ');\n throw new Error(\n `Invalid key '${key}' in 'permissionAttribute' config, expected ${valid}`,\n );\n }\n }\n\n const action = readStringOrStringArrayFromConfig(config, 'action', [\n 'create',\n 'read',\n 'update',\n 'delete',\n ]);\n\n const result = {\n ...(action ? { action } : {}),\n };\n\n return Object.keys(result).length ? result : undefined;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jose = require('jose');
|
|
4
|
+
var helpers = require('./helpers.cjs.js');
|
|
5
|
+
|
|
6
|
+
class JWKSHandler {
|
|
7
|
+
#entries = [];
|
|
8
|
+
add(config) {
|
|
9
|
+
if (!config.getString("options.url").match(/^\S+$/)) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"Illegal JWKS URL, must be a set of non-space characters"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
const algorithms = helpers.readStringOrStringArrayFromConfig(
|
|
15
|
+
config,
|
|
16
|
+
"options.algorithm"
|
|
17
|
+
);
|
|
18
|
+
const issuers = helpers.readStringOrStringArrayFromConfig(config, "options.issuer");
|
|
19
|
+
const audiences = helpers.readStringOrStringArrayFromConfig(
|
|
20
|
+
config,
|
|
21
|
+
"options.audience"
|
|
22
|
+
);
|
|
23
|
+
const subjectPrefix = config.getOptionalString("options.subjectPrefix");
|
|
24
|
+
const url = new URL(config.getString("options.url"));
|
|
25
|
+
const jwks = jose.createRemoteJWKSet(url);
|
|
26
|
+
const allAccessRestrictions = helpers.readAccessRestrictionsFromConfig(config);
|
|
27
|
+
this.#entries.push({
|
|
28
|
+
algorithms,
|
|
29
|
+
audiences,
|
|
30
|
+
issuers,
|
|
31
|
+
jwks,
|
|
32
|
+
subjectPrefix,
|
|
33
|
+
url,
|
|
34
|
+
allAccessRestrictions
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async verifyToken(token) {
|
|
38
|
+
for (const entry of this.#entries) {
|
|
39
|
+
try {
|
|
40
|
+
const {
|
|
41
|
+
payload: { sub }
|
|
42
|
+
} = await jose.jwtVerify(token, entry.jwks, {
|
|
43
|
+
algorithms: entry.algorithms,
|
|
44
|
+
issuer: entry.issuers,
|
|
45
|
+
audience: entry.audiences
|
|
46
|
+
});
|
|
47
|
+
if (sub) {
|
|
48
|
+
const prefix = entry.subjectPrefix ? `external:${entry.subjectPrefix}:` : "external:";
|
|
49
|
+
return {
|
|
50
|
+
subject: `${prefix}${sub}`,
|
|
51
|
+
allAccessRestrictions: entry.allAccessRestrictions
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exports.JWKSHandler = JWKSHandler;
|
|
63
|
+
//# sourceMappingURL=jwks.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.cjs.js","sources":["../../../../src/entrypoints/auth/external/jwks.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { jwtVerify, createRemoteJWKSet, JWTVerifyGetKey } from 'jose';\nimport { Config } from '@backstage/config';\nimport {\n readAccessRestrictionsFromConfig,\n readStringOrStringArrayFromConfig,\n} from './helpers';\nimport { AccessRestriptionsMap, TokenHandler } from './types';\n\n/**\n * Handles `type: jwks` access.\n *\n * @internal\n */\nexport class JWKSHandler implements TokenHandler {\n #entries: Array<{\n algorithms?: string[];\n audiences?: string[];\n issuers?: string[];\n subjectPrefix?: string;\n url: URL;\n jwks: JWTVerifyGetKey;\n allAccessRestrictions?: AccessRestriptionsMap;\n }> = [];\n\n add(config: Config) {\n if (!config.getString('options.url').match(/^\\S+$/)) {\n throw new Error(\n 'Illegal JWKS URL, must be a set of non-space characters',\n );\n }\n\n const algorithms = readStringOrStringArrayFromConfig(\n config,\n 'options.algorithm',\n );\n const issuers = readStringOrStringArrayFromConfig(config, 'options.issuer');\n const audiences = readStringOrStringArrayFromConfig(\n config,\n 'options.audience',\n );\n const subjectPrefix = config.getOptionalString('options.subjectPrefix');\n const url = new URL(config.getString('options.url'));\n const jwks = createRemoteJWKSet(url);\n const allAccessRestrictions = readAccessRestrictionsFromConfig(config);\n\n this.#entries.push({\n algorithms,\n audiences,\n issuers,\n jwks,\n subjectPrefix,\n url,\n allAccessRestrictions,\n });\n }\n\n async verifyToken(token: string) {\n for (const entry of this.#entries) {\n try {\n const {\n payload: { sub },\n } = await jwtVerify(token, entry.jwks, {\n algorithms: entry.algorithms,\n issuer: entry.issuers,\n audience: entry.audiences,\n });\n\n if (sub) {\n const prefix = entry.subjectPrefix\n ? `external:${entry.subjectPrefix}:`\n : 'external:';\n return {\n subject: `${prefix}${sub}`,\n allAccessRestrictions: entry.allAccessRestrictions,\n };\n }\n } catch {\n continue;\n }\n }\n return undefined;\n }\n}\n"],"names":["readStringOrStringArrayFromConfig","createRemoteJWKSet","readAccessRestrictionsFromConfig","jwtVerify"],"mappings":";;;;;AA6BO,MAAM,WAAoC,CAAA;AAAA,EAC/C,WAQK,EAAC,CAAA;AAAA,EAEN,IAAI,MAAgB,EAAA;AAClB,IAAA,IAAI,CAAC,MAAO,CAAA,SAAA,CAAU,aAAa,CAAE,CAAA,KAAA,CAAM,OAAO,CAAG,EAAA;AACnD,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,yDAAA;AAAA,OACF,CAAA;AAAA,KACF;AAEA,IAAA,MAAM,UAAa,GAAAA,yCAAA;AAAA,MACjB,MAAA;AAAA,MACA,mBAAA;AAAA,KACF,CAAA;AACA,IAAM,MAAA,OAAA,GAAUA,yCAAkC,CAAA,MAAA,EAAQ,gBAAgB,CAAA,CAAA;AAC1E,IAAA,MAAM,SAAY,GAAAA,yCAAA;AAAA,MAChB,MAAA;AAAA,MACA,kBAAA;AAAA,KACF,CAAA;AACA,IAAM,MAAA,aAAA,GAAgB,MAAO,CAAA,iBAAA,CAAkB,uBAAuB,CAAA,CAAA;AACtE,IAAA,MAAM,MAAM,IAAI,GAAA,CAAI,MAAO,CAAA,SAAA,CAAU,aAAa,CAAC,CAAA,CAAA;AACnD,IAAM,MAAA,IAAA,GAAOC,wBAAmB,GAAG,CAAA,CAAA;AACnC,IAAM,MAAA,qBAAA,GAAwBC,yCAAiC,MAAM,CAAA,CAAA;AAErE,IAAA,IAAA,CAAK,SAAS,IAAK,CAAA;AAAA,MACjB,UAAA;AAAA,MACA,SAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA;AAAA,MACA,aAAA;AAAA,MACA,GAAA;AAAA,MACA,qBAAA;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAAA,EAEA,MAAM,YAAY,KAAe,EAAA;AAC/B,IAAW,KAAA,MAAA,KAAA,IAAS,KAAK,QAAU,EAAA;AACjC,MAAI,IAAA;AACF,QAAM,MAAA;AAAA,UACJ,OAAA,EAAS,EAAE,GAAI,EAAA;AAAA,SACb,GAAA,MAAMC,cAAU,CAAA,KAAA,EAAO,MAAM,IAAM,EAAA;AAAA,UACrC,YAAY,KAAM,CAAA,UAAA;AAAA,UAClB,QAAQ,KAAM,CAAA,OAAA;AAAA,UACd,UAAU,KAAM,CAAA,SAAA;AAAA,SACjB,CAAA,CAAA;AAED,QAAA,IAAI,GAAK,EAAA;AACP,UAAA,MAAM,SAAS,KAAM,CAAA,aAAA,GACjB,CAAY,SAAA,EAAA,KAAA,CAAM,aAAa,CAC/B,CAAA,CAAA,GAAA,WAAA,CAAA;AACJ,UAAO,OAAA;AAAA,YACL,OAAS,EAAA,CAAA,EAAG,MAAM,CAAA,EAAG,GAAG,CAAA,CAAA;AAAA,YACxB,uBAAuB,KAAM,CAAA,qBAAA;AAAA,WAC/B,CAAA;AAAA,SACF;AAAA,OACM,CAAA,MAAA;AACN,QAAA,SAAA;AAAA,OACF;AAAA,KACF;AACA,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACT;AACF;;;;"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jose = require('jose');
|
|
4
|
+
var helpers = require('./helpers.cjs.js');
|
|
5
|
+
|
|
6
|
+
class LegacyTokenHandler {
|
|
7
|
+
#entries = new Array();
|
|
8
|
+
add(config) {
|
|
9
|
+
const allAccessRestrictions = helpers.readAccessRestrictionsFromConfig(config);
|
|
10
|
+
this.#doAdd(
|
|
11
|
+
config.getString("options.secret"),
|
|
12
|
+
config.getString("options.subject"),
|
|
13
|
+
allAccessRestrictions
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
// used only for the old backend.auth.keys array
|
|
17
|
+
addOld(config) {
|
|
18
|
+
this.#doAdd(config.getString("secret"), "external:backstage-plugin");
|
|
19
|
+
}
|
|
20
|
+
#doAdd(secret, subject, allAccessRestrictions) {
|
|
21
|
+
if (!secret.match(/^\S+$/)) {
|
|
22
|
+
throw new Error("Illegal secret, must be a valid base64 string");
|
|
23
|
+
} else if (!subject.match(/^\S+$/)) {
|
|
24
|
+
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
25
|
+
}
|
|
26
|
+
let key;
|
|
27
|
+
try {
|
|
28
|
+
key = jose.base64url.decode(secret);
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error("Illegal secret, must be a valid base64 string");
|
|
31
|
+
}
|
|
32
|
+
if (this.#entries.some((e) => e.key === key)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Legacy externalAccess token was declared more than once"
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
this.#entries.push({
|
|
38
|
+
key,
|
|
39
|
+
result: {
|
|
40
|
+
subject,
|
|
41
|
+
allAccessRestrictions
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async verifyToken(token) {
|
|
46
|
+
try {
|
|
47
|
+
const { alg } = jose.decodeProtectedHeader(token);
|
|
48
|
+
if (alg !== "HS256") {
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
const { sub, aud } = jose.decodeJwt(token);
|
|
52
|
+
if (sub !== "backstage-server" || aud) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
} catch (e) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
for (const { key, result } of this.#entries) {
|
|
59
|
+
try {
|
|
60
|
+
await jose.jwtVerify(token, key);
|
|
61
|
+
return result;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
if (e.code !== "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
exports.LegacyTokenHandler = LegacyTokenHandler;
|
|
73
|
+
//# sourceMappingURL=legacy.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy.cjs.js","sources":["../../../../src/entrypoints/auth/external/legacy.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { base64url, decodeJwt, decodeProtectedHeader, jwtVerify } from 'jose';\nimport { readAccessRestrictionsFromConfig } from './helpers';\nimport { AccessRestriptionsMap, TokenHandler } from './types';\n\n/**\n * Handles `type: legacy` access.\n *\n * @internal\n */\nexport class LegacyTokenHandler implements TokenHandler {\n #entries = new Array<{\n key: Uint8Array;\n result: {\n subject: string;\n allAccessRestrictions?: AccessRestriptionsMap;\n };\n }>();\n\n add(config: Config) {\n const allAccessRestrictions = readAccessRestrictionsFromConfig(config);\n this.#doAdd(\n config.getString('options.secret'),\n config.getString('options.subject'),\n allAccessRestrictions,\n );\n }\n\n // used only for the old backend.auth.keys array\n addOld(config: Config) {\n // This choice of subject is for compatibility reasons\n this.#doAdd(config.getString('secret'), 'external:backstage-plugin');\n }\n\n #doAdd(\n secret: string,\n subject: string,\n allAccessRestrictions?: AccessRestriptionsMap,\n ) {\n if (!secret.match(/^\\S+$/)) {\n throw new Error('Illegal secret, must be a valid base64 string');\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n }\n\n let key: Uint8Array;\n try {\n key = base64url.decode(secret);\n } catch {\n throw new Error('Illegal secret, must be a valid base64 string');\n }\n\n if (this.#entries.some(e => e.key === key)) {\n throw new Error(\n 'Legacy externalAccess token was declared more than once',\n );\n }\n\n this.#entries.push({\n key,\n result: {\n subject,\n allAccessRestrictions,\n },\n });\n }\n\n async verifyToken(token: string) {\n // First do a duck typing check to see if it remotely looks like a legacy token\n try {\n // We do a fair amount of checking upfront here. Since we aren't certain\n // that it's even the right type of key that we're looking at, we can't\n // defer eg the alg check to jwtVerify, because it won't be possible to\n // discern different reasons for key verification failures from each other\n // easily\n const { alg } = decodeProtectedHeader(token);\n if (alg !== 'HS256') {\n return undefined;\n }\n const { sub, aud } = decodeJwt(token);\n if (sub !== 'backstage-server' || aud) {\n return undefined;\n }\n } catch (e) {\n // Doesn't look like a jwt at all\n return undefined;\n }\n\n for (const { key, result } of this.#entries) {\n try {\n await jwtVerify(token, key);\n return result;\n } catch (e) {\n if (e.code !== 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {\n throw e;\n }\n // Otherwise continue to try the next key\n }\n }\n\n // None of the signing keys matched\n return undefined;\n }\n}\n"],"names":["readAccessRestrictionsFromConfig","base64url","decodeProtectedHeader","decodeJwt","jwtVerify"],"mappings":";;;;;AA0BO,MAAM,kBAA2C,CAAA;AAAA,EACtD,QAAA,GAAW,IAAI,KAMZ,EAAA,CAAA;AAAA,EAEH,IAAI,MAAgB,EAAA;AAClB,IAAM,MAAA,qBAAA,GAAwBA,yCAAiC,MAAM,CAAA,CAAA;AACrE,IAAK,IAAA,CAAA,MAAA;AAAA,MACH,MAAA,CAAO,UAAU,gBAAgB,CAAA;AAAA,MACjC,MAAA,CAAO,UAAU,iBAAiB,CAAA;AAAA,MAClC,qBAAA;AAAA,KACF,CAAA;AAAA,GACF;AAAA;AAAA,EAGA,OAAO,MAAgB,EAAA;AAErB,IAAA,IAAA,CAAK,MAAO,CAAA,MAAA,CAAO,SAAU,CAAA,QAAQ,GAAG,2BAA2B,CAAA,CAAA;AAAA,GACrE;AAAA,EAEA,MAAA,CACE,MACA,EAAA,OAAA,EACA,qBACA,EAAA;AACA,IAAA,IAAI,CAAC,MAAA,CAAO,KAAM,CAAA,OAAO,CAAG,EAAA;AAC1B,MAAM,MAAA,IAAI,MAAM,+CAA+C,CAAA,CAAA;AAAA,KACtD,MAAA,IAAA,CAAC,OAAQ,CAAA,KAAA,CAAM,OAAO,CAAG,EAAA;AAClC,MAAM,MAAA,IAAI,MAAM,wDAAwD,CAAA,CAAA;AAAA,KAC1E;AAEA,IAAI,IAAA,GAAA,CAAA;AACJ,IAAI,IAAA;AACF,MAAM,GAAA,GAAAC,cAAA,CAAU,OAAO,MAAM,CAAA,CAAA;AAAA,KACvB,CAAA,MAAA;AACN,MAAM,MAAA,IAAI,MAAM,+CAA+C,CAAA,CAAA;AAAA,KACjE;AAEA,IAAA,IAAI,KAAK,QAAS,CAAA,IAAA,CAAK,OAAK,CAAE,CAAA,GAAA,KAAQ,GAAG,CAAG,EAAA;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,yDAAA;AAAA,OACF,CAAA;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,SAAS,IAAK,CAAA;AAAA,MACjB,GAAA;AAAA,MACA,MAAQ,EAAA;AAAA,QACN,OAAA;AAAA,QACA,qBAAA;AAAA,OACF;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAAA,EAEA,MAAM,YAAY,KAAe,EAAA;AAE/B,IAAI,IAAA;AAMF,MAAA,MAAM,EAAE,GAAA,EAAQ,GAAAC,0BAAA,CAAsB,KAAK,CAAA,CAAA;AAC3C,MAAA,IAAI,QAAQ,OAAS,EAAA;AACnB,QAAO,OAAA,KAAA,CAAA,CAAA;AAAA,OACT;AACA,MAAA,MAAM,EAAE,GAAA,EAAK,GAAI,EAAA,GAAIC,eAAU,KAAK,CAAA,CAAA;AACpC,MAAI,IAAA,GAAA,KAAQ,sBAAsB,GAAK,EAAA;AACrC,QAAO,OAAA,KAAA,CAAA,CAAA;AAAA,OACT;AAAA,aACO,CAAG,EAAA;AAEV,MAAO,OAAA,KAAA,CAAA,CAAA;AAAA,KACT;AAEA,IAAA,KAAA,MAAW,EAAE,GAAA,EAAK,MAAO,EAAA,IAAK,KAAK,QAAU,EAAA;AAC3C,MAAI,IAAA;AACF,QAAM,MAAAC,cAAA,CAAU,OAAO,GAAG,CAAA,CAAA;AAC1B,QAAO,OAAA,MAAA,CAAA;AAAA,eACA,CAAG,EAAA;AACV,QAAI,IAAA,CAAA,CAAE,SAAS,uCAAyC,EAAA;AACtD,UAAM,MAAA,CAAA,CAAA;AAAA,SACR;AAAA,OAEF;AAAA,KACF;AAGA,IAAO,OAAA,KAAA,CAAA,CAAA;AAAA,GACT;AACF;;;;"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var helpers = require('./helpers.cjs.js');
|
|
4
|
+
|
|
5
|
+
const MIN_TOKEN_LENGTH = 8;
|
|
6
|
+
class StaticTokenHandler {
|
|
7
|
+
#entries = /* @__PURE__ */ new Map();
|
|
8
|
+
add(config) {
|
|
9
|
+
const token = config.getString("options.token");
|
|
10
|
+
const subject = config.getString("options.subject");
|
|
11
|
+
const allAccessRestrictions = helpers.readAccessRestrictionsFromConfig(config);
|
|
12
|
+
if (!token.match(/^\S+$/)) {
|
|
13
|
+
throw new Error("Illegal token, must be a set of non-space characters");
|
|
14
|
+
} else if (token.length < MIN_TOKEN_LENGTH) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`
|
|
17
|
+
);
|
|
18
|
+
} else if (!subject.match(/^\S+$/)) {
|
|
19
|
+
throw new Error("Illegal subject, must be a set of non-space characters");
|
|
20
|
+
} else if (this.#entries.has(token)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Static externalAccess token was declared more than once"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
this.#entries.set(token, { subject, allAccessRestrictions });
|
|
26
|
+
}
|
|
27
|
+
async verifyToken(token) {
|
|
28
|
+
return this.#entries.get(token);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.StaticTokenHandler = StaticTokenHandler;
|
|
33
|
+
//# sourceMappingURL=static.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.cjs.js","sources":["../../../../src/entrypoints/auth/external/static.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { readAccessRestrictionsFromConfig } from './helpers';\nimport { AccessRestriptionsMap, TokenHandler } from './types';\n\nconst MIN_TOKEN_LENGTH = 8;\n\n/**\n * Handles `type: static` access.\n *\n * @internal\n */\nexport class StaticTokenHandler implements TokenHandler {\n #entries = new Map<\n string,\n {\n subject: string;\n allAccessRestrictions?: AccessRestriptionsMap;\n }\n >();\n\n add(config: Config) {\n const token = config.getString('options.token');\n const subject = config.getString('options.subject');\n const allAccessRestrictions = readAccessRestrictionsFromConfig(config);\n\n if (!token.match(/^\\S+$/)) {\n throw new Error('Illegal token, must be a set of non-space characters');\n } else if (token.length < MIN_TOKEN_LENGTH) {\n throw new Error(\n `Illegal token, must be at least ${MIN_TOKEN_LENGTH} characters length`,\n );\n } else if (!subject.match(/^\\S+$/)) {\n throw new Error('Illegal subject, must be a set of non-space characters');\n } else if (this.#entries.has(token)) {\n throw new Error(\n 'Static externalAccess token was declared more than once',\n );\n }\n\n this.#entries.set(token, { subject, allAccessRestrictions });\n }\n\n async verifyToken(token: string) {\n return this.#entries.get(token);\n }\n}\n"],"names":["readAccessRestrictionsFromConfig"],"mappings":";;;;AAoBA,MAAM,gBAAmB,GAAA,CAAA,CAAA;AAOlB,MAAM,kBAA2C,CAAA;AAAA,EACtD,QAAA,uBAAe,GAMb,EAAA,CAAA;AAAA,EAEF,IAAI,MAAgB,EAAA;AAClB,IAAM,MAAA,KAAA,GAAQ,MAAO,CAAA,SAAA,CAAU,eAAe,CAAA,CAAA;AAC9C,IAAM,MAAA,OAAA,GAAU,MAAO,CAAA,SAAA,CAAU,iBAAiB,CAAA,CAAA;AAClD,IAAM,MAAA,qBAAA,GAAwBA,yCAAiC,MAAM,CAAA,CAAA;AAErE,IAAA,IAAI,CAAC,KAAA,CAAM,KAAM,CAAA,OAAO,CAAG,EAAA;AACzB,MAAM,MAAA,IAAI,MAAM,sDAAsD,CAAA,CAAA;AAAA,KACxE,MAAA,IAAW,KAAM,CAAA,MAAA,GAAS,gBAAkB,EAAA;AAC1C,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,mCAAmC,gBAAgB,CAAA,kBAAA,CAAA;AAAA,OACrD,CAAA;AAAA,KACS,MAAA,IAAA,CAAC,OAAQ,CAAA,KAAA,CAAM,OAAO,CAAG,EAAA;AAClC,MAAM,MAAA,IAAI,MAAM,wDAAwD,CAAA,CAAA;AAAA,KAC/D,MAAA,IAAA,IAAA,CAAK,QAAS,CAAA,GAAA,CAAI,KAAK,CAAG,EAAA;AACnC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,yDAAA;AAAA,OACF,CAAA;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,SAAS,GAAI,CAAA,KAAA,EAAO,EAAE,OAAA,EAAS,uBAAuB,CAAA,CAAA;AAAA,GAC7D;AAAA,EAEA,MAAM,YAAY,KAAe,EAAA;AAC/B,IAAO,OAAA,IAAA,CAAK,QAAS,CAAA,GAAA,CAAI,KAAK,CAAA,CAAA;AAAA,GAChC;AACF;;;;"}
|
|
@@ -50,4 +50,4 @@ exports.createCredentialsWithNonePrincipal = createCredentialsWithNonePrincipal;
|
|
|
50
50
|
exports.createCredentialsWithServicePrincipal = createCredentialsWithServicePrincipal;
|
|
51
51
|
exports.createCredentialsWithUserPrincipal = createCredentialsWithUserPrincipal;
|
|
52
52
|
exports.toInternalBackstageCredentials = toInternalBackstageCredentials;
|
|
53
|
-
//# sourceMappingURL=helpers
|
|
53
|
+
//# sourceMappingURL=helpers.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.cjs.js","sources":["../../../src/entrypoints/auth/helpers.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n BackstageCredentials,\n BackstageNonePrincipal,\n BackstagePrincipalAccessRestrictions,\n BackstageServicePrincipal,\n BackstageUserPrincipal,\n} from '@backstage/backend-plugin-api';\nimport { InternalBackstageCredentials } from './types';\n\nexport function createCredentialsWithServicePrincipal(\n sub: string,\n token?: string,\n accessRestrictions?: BackstagePrincipalAccessRestrictions,\n): InternalBackstageCredentials<BackstageServicePrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n token,\n principal: {\n type: 'service',\n subject: sub,\n accessRestrictions,\n },\n };\n}\n\nexport function createCredentialsWithUserPrincipal(\n sub: string,\n token: string,\n expiresAt?: Date,\n): InternalBackstageCredentials<BackstageUserPrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n token,\n expiresAt,\n principal: {\n type: 'user',\n userEntityRef: sub,\n },\n };\n}\n\nexport function createCredentialsWithNonePrincipal(): InternalBackstageCredentials<BackstageNonePrincipal> {\n return {\n $$type: '@backstage/BackstageCredentials',\n version: 'v1',\n principal: {\n type: 'none',\n },\n };\n}\n\nexport function toInternalBackstageCredentials(\n credentials: BackstageCredentials,\n): InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n> {\n if (credentials.$$type !== '@backstage/BackstageCredentials') {\n throw new Error('Invalid credential type');\n }\n\n const internalCredentials = credentials as InternalBackstageCredentials<\n BackstageUserPrincipal | BackstageServicePrincipal | BackstageNonePrincipal\n >;\n\n if (internalCredentials.version !== 'v1') {\n throw new Error(\n `Invalid credential version ${internalCredentials.version}`,\n );\n }\n\n return internalCredentials;\n}\n"],"names":[],"mappings":";;AAyBgB,SAAA,qCAAA,CACd,GACA,EAAA,KAAA,EACA,kBACyD,EAAA;AACzD,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,KAAA;AAAA,IACA,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,SAAA;AAAA,MACN,OAAS,EAAA,GAAA;AAAA,MACT,kBAAA;AAAA,KACF;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,kCAAA,CACd,GACA,EAAA,KAAA,EACA,SACsD,EAAA;AACtD,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,KAAA;AAAA,IACA,SAAA;AAAA,IACA,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,MAAA;AAAA,MACN,aAAe,EAAA,GAAA;AAAA,KACjB;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,kCAA2F,GAAA;AACzG,EAAO,OAAA;AAAA,IACL,MAAQ,EAAA,iCAAA;AAAA,IACR,OAAS,EAAA,IAAA;AAAA,IACT,SAAW,EAAA;AAAA,MACT,IAAM,EAAA,MAAA;AAAA,KACR;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,+BACd,WAGA,EAAA;AACA,EAAI,IAAA,WAAA,CAAY,WAAW,iCAAmC,EAAA;AAC5D,IAAM,MAAA,IAAI,MAAM,yBAAyB,CAAA,CAAA;AAAA,GAC3C;AAEA,EAAA,MAAM,mBAAsB,GAAA,WAAA,CAAA;AAI5B,EAAI,IAAA,mBAAA,CAAoB,YAAY,IAAM,EAAA;AACxC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,oBAAoB,OAAO,CAAA,CAAA;AAAA,KAC3D,CAAA;AAAA,GACF;AAEA,EAAO,OAAA,mBAAA,CAAA;AACT;;;;;;;"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var jose = require('jose');
|
|
4
|
+
var errors = require('@backstage/errors');
|
|
5
|
+
var pluginAuthNode = require('@backstage/plugin-auth-node');
|
|
6
|
+
var JwksClient = require('../JwksClient.cjs.js');
|
|
7
|
+
var types = require('@backstage/types');
|
|
8
|
+
|
|
9
|
+
const SECONDS_IN_MS = 1e3;
|
|
10
|
+
const ALLOWED_PLUGIN_ID_PATTERN = /^[a-z0-9_-]+$/i;
|
|
11
|
+
class PluginTokenHandler {
|
|
12
|
+
constructor(logger, ownPluginId, keySource, algorithm, keyDurationSeconds, discovery) {
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.ownPluginId = ownPluginId;
|
|
15
|
+
this.keySource = keySource;
|
|
16
|
+
this.algorithm = algorithm;
|
|
17
|
+
this.keyDurationSeconds = keyDurationSeconds;
|
|
18
|
+
this.discovery = discovery;
|
|
19
|
+
}
|
|
20
|
+
jwksMap = /* @__PURE__ */ new Map();
|
|
21
|
+
// Tracking state for isTargetPluginSupported
|
|
22
|
+
supportedTargetPlugins = /* @__PURE__ */ new Set();
|
|
23
|
+
targetPluginInflightChecks = /* @__PURE__ */ new Map();
|
|
24
|
+
static create(options) {
|
|
25
|
+
return new PluginTokenHandler(
|
|
26
|
+
options.logger,
|
|
27
|
+
options.ownPluginId,
|
|
28
|
+
options.keySource,
|
|
29
|
+
options.algorithm ?? "ES256",
|
|
30
|
+
Math.round(types.durationToMilliseconds(options.keyDuration) / 1e3),
|
|
31
|
+
options.discovery
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
async verifyToken(token) {
|
|
35
|
+
try {
|
|
36
|
+
const { typ } = jose.decodeProtectedHeader(token);
|
|
37
|
+
if (typ !== pluginAuthNode.tokenTypes.plugin.typParam) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
return void 0;
|
|
42
|
+
}
|
|
43
|
+
const pluginId = String(jose.decodeJwt(token).sub);
|
|
44
|
+
if (!pluginId) {
|
|
45
|
+
throw new errors.AuthenticationError("Invalid plugin token: missing subject");
|
|
46
|
+
}
|
|
47
|
+
if (!ALLOWED_PLUGIN_ID_PATTERN.test(pluginId)) {
|
|
48
|
+
throw new errors.AuthenticationError(
|
|
49
|
+
"Invalid plugin token: forbidden subject format"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const jwksClient = await this.getJwksClient(pluginId);
|
|
53
|
+
await jwksClient.refreshKeyStore(token);
|
|
54
|
+
const { payload } = await jose.jwtVerify(
|
|
55
|
+
token,
|
|
56
|
+
jwksClient.getKey,
|
|
57
|
+
{
|
|
58
|
+
typ: pluginAuthNode.tokenTypes.plugin.typParam,
|
|
59
|
+
audience: this.ownPluginId,
|
|
60
|
+
requiredClaims: ["iat", "exp", "sub", "aud"]
|
|
61
|
+
}
|
|
62
|
+
).catch((e) => {
|
|
63
|
+
throw new errors.AuthenticationError("Invalid plugin token", e);
|
|
64
|
+
});
|
|
65
|
+
return { subject: `plugin:${payload.sub}`, limitedUserToken: payload.obo };
|
|
66
|
+
}
|
|
67
|
+
async issueToken(options) {
|
|
68
|
+
const { pluginId, targetPluginId, onBehalfOf } = options;
|
|
69
|
+
const key = await this.keySource.getPrivateSigningKey();
|
|
70
|
+
const sub = pluginId;
|
|
71
|
+
const aud = targetPluginId;
|
|
72
|
+
const iat = Math.floor(Date.now() / SECONDS_IN_MS);
|
|
73
|
+
const ourExp = iat + this.keyDurationSeconds;
|
|
74
|
+
const exp = onBehalfOf ? Math.min(
|
|
75
|
+
ourExp,
|
|
76
|
+
Math.floor(onBehalfOf.expiresAt.getTime() / SECONDS_IN_MS)
|
|
77
|
+
) : ourExp;
|
|
78
|
+
const claims = { sub, aud, iat, exp, obo: onBehalfOf?.token };
|
|
79
|
+
const token = await new jose.SignJWT(claims).setProtectedHeader({
|
|
80
|
+
typ: pluginAuthNode.tokenTypes.plugin.typParam,
|
|
81
|
+
alg: this.algorithm,
|
|
82
|
+
kid: key.kid
|
|
83
|
+
}).setAudience(aud).setSubject(sub).setIssuedAt(iat).setExpirationTime(exp).sign(await jose.importJWK(key));
|
|
84
|
+
return { token };
|
|
85
|
+
}
|
|
86
|
+
async isTargetPluginSupported(targetPluginId) {
|
|
87
|
+
if (this.supportedTargetPlugins.has(targetPluginId)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const inFlight = this.targetPluginInflightChecks.get(targetPluginId);
|
|
91
|
+
if (inFlight) {
|
|
92
|
+
return inFlight;
|
|
93
|
+
}
|
|
94
|
+
const doCheck = async () => {
|
|
95
|
+
try {
|
|
96
|
+
const res = await fetch(
|
|
97
|
+
`${await this.discovery.getBaseUrl(
|
|
98
|
+
targetPluginId
|
|
99
|
+
)}/.backstage/auth/v1/jwks.json`
|
|
100
|
+
);
|
|
101
|
+
if (res.status === 404) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
throw new Error(`Failed to fetch jwks.json, ${res.status}`);
|
|
106
|
+
}
|
|
107
|
+
const data = await res.json();
|
|
108
|
+
if (!data.keys) {
|
|
109
|
+
throw new Error(`Invalid jwks.json response, missing keys`);
|
|
110
|
+
}
|
|
111
|
+
this.supportedTargetPlugins.add(targetPluginId);
|
|
112
|
+
return true;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.logger.error("Unexpected failure for target JWKS check", error);
|
|
115
|
+
return false;
|
|
116
|
+
} finally {
|
|
117
|
+
this.targetPluginInflightChecks.delete(targetPluginId);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const check = doCheck();
|
|
121
|
+
this.targetPluginInflightChecks.set(targetPluginId, check);
|
|
122
|
+
return check;
|
|
123
|
+
}
|
|
124
|
+
async getJwksClient(pluginId) {
|
|
125
|
+
const client = this.jwksMap.get(pluginId);
|
|
126
|
+
if (client) {
|
|
127
|
+
return client;
|
|
128
|
+
}
|
|
129
|
+
if (!await this.isTargetPluginSupported(pluginId)) {
|
|
130
|
+
throw new errors.AuthenticationError(
|
|
131
|
+
`Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. The target plugin needs to be migrated to be installed in an app using the new backend system.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const newClient = new JwksClient.JwksClient(async () => {
|
|
135
|
+
return new URL(
|
|
136
|
+
`${await this.discovery.getBaseUrl(
|
|
137
|
+
pluginId
|
|
138
|
+
)}/.backstage/auth/v1/jwks.json`
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
this.jwksMap.set(pluginId, newClient);
|
|
142
|
+
return newClient;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
exports.PluginTokenHandler = PluginTokenHandler;
|
|
147
|
+
//# sourceMappingURL=PluginTokenHandler.cjs.js.map
|