From 293c835e05370a46b12f5fc020a8202ff5d69667 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 1 Nov 2023 13:50:11 +1100 Subject: [PATCH 1/7] Add support for Service Tokens to the cfaccess auth provider Signed-off-by: Tyler Davis --- .changeset/soft-otters-report.md | 5 + .../cloudflare-access/provider.test.ts | 97 +++++++++++++++++++ .../providers/cloudflare-access/provider.ts | 38 ++++++-- 3 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 .changeset/soft-otters-report.md diff --git a/.changeset/soft-otters-report.md b/.changeset/soft-otters-report.md new file mode 100644 index 0000000000..d71d3a2e9a --- /dev/null +++ b/.changeset/soft-otters-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': minor +--- + +Add support for Service Tokens to Cloudflare Access auth provider diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts index 95e4b0901b..f34795fa0d 100644 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts +++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts @@ -34,6 +34,15 @@ const mockClaims = { exp: 1632833763, iss: 'ISSUER_URL', }; +const mockServiceTokenJwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWV9JRCIsImlzcyI6IklTU1VFUl9VUkwifQ.eyJzdWIiOiIiLCJuYW1lIjoiQm90IiwiY29tbW9uX25hbWUiOiJ0ZXN0X3Rva2VuX2lkLmFjY2VzcyIsImlhdCI6MTUxNjIzOTAyMn0.KEe-qBHuN8HKh1LobtDQnCJ3rxZOhW-lMSDad8uV_l0'; +const mockServiceTokenClaims = { + sub: '', + common_name: 'test_token_id.access', + iat: 1632833760, + exp: 1632833763, + iss: 'ISSUER_URL', +}; const mockCfIdentity = { name: 'foo', id: '123', @@ -78,6 +87,32 @@ const identityOkResponse = { }, }; +const identityOkServiceTokenResponse = { + backstageIdentity: { + expiresInSeconds: undefined, + identity: { + ownershipEntityRefs: ['user:default/jimmymarkum'], + type: 'user', + userEntityRef: 'user:default/jimmymarkum', + }, + token: + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', + }, + profile: { + email: undefined, + }, + providerInfo: { + cfAccessIdentityProfile: { + email: 'test_token_id.access@foobar.com', + groups: [], + id: 'test_token_id.access', + name: 'Bot', + }, + claims: mockServiceTokenClaims, + expiresInSeconds: 3, + }, +}; + const mockAuthenticatedUserEmail = 'user.name@email.test'; const mockCacheClient = { get: jest.fn(), @@ -121,6 +156,12 @@ describe('CloudflareAccessAuthProvider', () => { }, } as unknown as express.Request; + const mockRequestWithSericeTokenJwtHeader = { + header: jest.fn(() => { + return mockServiceTokenJwt; + }), + } as unknown as express.Request; + const mockRequestWithoutJwt = { header: jest.fn(_ => { return undefined; @@ -169,7 +210,63 @@ describe('CloudflareAccessAuthProvider', () => { cache: mockCacheClient, }); + const providerServiceToken = new CloudflareAccessAuthProvider({ + teamName: 'foobar', + resolverContext: {} as AuthResolverContext, + authHandler: async result => { + expect(result).toEqual( + expect.objectContaining({ + claims: mockServiceTokenClaims, + cfIdentity: { + email: 'test_token_id.access@foobar.com', + groups: [], + id: 'test_token_id.access', + name: 'Bot', + }, + token: mockServiceTokenJwt, + }), + ); + return { + profile: { + email: result.claims.email, + }, + }; + }, + signInResolver: async ({ result }) => { + expect(result).toEqual( + expect.objectContaining({ + claims: mockServiceTokenClaims, + cfIdentity: { + email: 'test_token_id.access@foobar.com', + groups: [], + id: 'test_token_id.access', + name: 'Bot', + }, + token: mockServiceTokenJwt, + }), + ); + return { + token: + 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob', + }; + }, + cache: mockCacheClient, + }); + describe('when JWT is valid', () => { + it('validates a service token JWT without calling get-identity', async () => { + jwtMock.mockReturnValue( + Promise.resolve({ payload: mockServiceTokenClaims }), + ); + await providerServiceToken.refresh( + mockRequestWithSericeTokenJwtHeader, + mockResponse, + ); + expect(mockResponse.json).toHaveBeenCalledWith( + identityOkServiceTokenResponse, + ); + }); + it('returns cfidentity also when get-identity succeeds', async () => { jwtMock.mockReturnValue(Promise.resolve({ payload: mockClaims })); mockFetch.mockReturnValueOnce( diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts index fe271f9058..c6217e6523 100644 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts +++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts @@ -17,7 +17,6 @@ import { AuthHandler } from '../types'; import fetch, { Headers } from 'node-fetch'; import express from 'express'; -import * as _ from 'lodash'; import { jwtVerify, createRemoteJWKSet } from 'jose'; import { AuthenticationError, @@ -260,8 +259,20 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { const verifyResult = await jwtVerify(jwt, this.jwtKeySet, { issuer: `https://${this.teamName}.cloudflareaccess.com`, }); - const sub = verifyResult.payload.sub; - const cfAccessResultStr = await this.cache?.get(`${CACHE_PREFIX}/${sub}`); + + const isServiceToken = verifyResult.payload.sub === ''; + + const subject = isServiceToken + ? (verifyResult.payload.common_name as string) + : verifyResult.payload.sub; + if (!subject) { + throw new AuthenticationError( + `Missing both sub and common_name from Cloudflare Access JWT`, + ); + } + + const cacheKey = `${CACHE_PREFIX}/${subject}`; + const cfAccessResultStr = await this.cache?.get(cacheKey); if (typeof cfAccessResultStr === 'string') { const result = JSON.parse(cfAccessResultStr) as CloudflareAccessResult; return { @@ -270,12 +281,23 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { }; } const claims = verifyResult.payload as CloudflareAccessClaims; + // Builds a passport profile from JWT claims first try { - // If we successfully fetch the get-identity endpoint, - // We supplement the passport profile with richer user identity - // information here. - const cfIdentity = await this.getIdentityProfile(jwt); + let cfIdentity: CloudflareAccessIdentityProfile; + if (isServiceToken) { + cfIdentity = { + id: subject, + name: 'Bot', + email: `${subject}@${this.teamName}.com`, + groups: [], + }; + } else { + // If we successfully fetch the get-identity endpoint, + // We supplement the passport profile with richer user identity + // information here. + cfIdentity = await this.getIdentityProfile(jwt); + } // Stores a stringified JSON object in cfaccess provider cache only when // we complete all steps const cfAccessResult = { @@ -283,7 +305,7 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { cfIdentity, expiresInSeconds: claims.exp - claims.iat, }; - this.cache?.set(`${CACHE_PREFIX}/${sub}`, JSON.stringify(cfAccessResult)); + this.cache?.set(cacheKey, JSON.stringify(cfAccessResult)); return { ...cfAccessResult, token: jwt, From 3191d616e835bba03f14007090e041d0869efdfd Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 7 Feb 2024 20:26:04 +1100 Subject: [PATCH 2/7] pr feedback: make service tokens configurable Signed-off-by: Tyler Davis --- docs/auth/cloudflare/access.md | 2 ++ .../cloudflare-access/provider.test.ts | 31 +++++++++++++++++++ .../providers/cloudflare-access/provider.ts | 31 +++++++++++++++++-- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/auth/cloudflare/access.md b/docs/auth/cloudflare/access.md index b1d8e4c676..457c3abd3e 100644 --- a/docs/auth/cloudflare/access.md +++ b/docs/auth/cloudflare/access.md @@ -25,6 +25,8 @@ auth: providers: cfaccess: teamName: + serviceTokens: + "1uh2fh19efvfh129f1f919u21f2f19jf2.access": "bot-user@your-company.com ``` You can find the team name in the Cloudflare Zero Trust dashboard. diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts index f34795fa0d..f5f1f361ab 100644 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts +++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts @@ -43,6 +43,15 @@ const mockServiceTokenClaims = { exp: 1632833763, iss: 'ISSUER_URL', }; +const mockServiceTokenDisallowedJwt = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWV9JRCIsImlzcyI6IklTU1VFUl9VUkwifQ.eyJzdWIiOiIiLCJuYW1lIjoiQm90IiwiY29tbW9uX25hbWUiOiJzb21lX290aGVyX3Rva2VuX2lkLmFjY2VzcyIsImlhdCI6MTUxNjIzOTAyMn0.qQeeQW_urYrrTq-tuKZWURwTUrjzgyFyZA9ViQtD-FM'; +const mockServiceTokenDisallowedClaims = { + sub: '', + common_name: 'some_other_token_id.access', + iat: 1632833760, + exp: 1632833763, + iss: 'ISSUER_URL', +}; const mockCfIdentity = { name: 'foo', id: '123', @@ -162,6 +171,12 @@ describe('CloudflareAccessAuthProvider', () => { }), } as unknown as express.Request; + const mockRequestWithSericeTokenDisallowedJwtHeader = { + header: jest.fn(() => { + return mockServiceTokenDisallowedJwt; + }), + } as unknown as express.Request; + const mockRequestWithoutJwt = { header: jest.fn(_ => { return undefined; @@ -179,6 +194,7 @@ describe('CloudflareAccessAuthProvider', () => { const provider = new CloudflareAccessAuthProvider({ teamName: 'foobar', + serviceTokens: {}, resolverContext: {} as AuthResolverContext, authHandler: async result => { expect(result).toEqual( @@ -212,6 +228,9 @@ describe('CloudflareAccessAuthProvider', () => { const providerServiceToken = new CloudflareAccessAuthProvider({ teamName: 'foobar', + serviceTokens: { + 'test_token_id.access': 'test_token_id.access@foobar.com', + }, resolverContext: {} as AuthResolverContext, authHandler: async result => { expect(result).toEqual( @@ -267,6 +286,18 @@ describe('CloudflareAccessAuthProvider', () => { ); }); + it('rejects a disallowed service token JWT without calling get-identity', async () => { + jwtMock.mockReturnValue( + Promise.resolve({ payload: mockServiceTokenDisallowedClaims }), + ); + await expect( + providerServiceToken.refresh( + mockRequestWithSericeTokenDisallowedJwtHeader, + mockResponse, + ), + ).rejects.toThrow(); + }); + it('returns cfidentity also when get-identity succeeds', async () => { jwtMock.mockReturnValue(Promise.resolve({ payload: mockClaims })); mockFetch.mockReturnValueOnce( diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts index c6217e6523..31e6e6d391 100644 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts +++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts @@ -48,6 +48,8 @@ const CACHE_PREFIX = 'providers/cloudflare-access/profile-v1'; */ export const CF_DEFAULT_CACHE_TTL = 3600; +type ServiceTokens = Record; + /** @public */ export type Options = { /** @@ -58,6 +60,15 @@ export type Options = { * https://.cloudflareaccess.com/cdn-cgi/access/certs */ teamName: string; + /** + * Allowed Cloudflare Service Tokens + * + * Cloudflare does not currently allow assigning any sort of identity to + * Service Tokens. Therefore, this allows you to build an allow list mapping + * the Client ID of any Service Tokens that should be allowed to pass the + * auth check to the identity (email) you would like to associate with it. + */ + serviceTokens: ServiceTokens; authHandler: AuthHandler; signInResolver: SignInResolver; resolverContext: AuthResolverContext; @@ -178,6 +189,7 @@ export type CloudflareAccessResponse = export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { private readonly teamName: string; + private readonly serviceTokens: ServiceTokens; private readonly resolverContext: AuthResolverContext; private readonly authHandler: AuthHandler; private readonly signInResolver: SignInResolver; @@ -186,6 +198,7 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { constructor(options: Options) { this.teamName = options.teamName; + this.serviceTokens = options.serviceTokens; this.authHandler = options.authHandler; this.signInResolver = options.signInResolver; this.resolverContext = options.resolverContext; @@ -260,7 +273,7 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { issuer: `https://${this.teamName}.cloudflareaccess.com`, }); - const isServiceToken = verifyResult.payload.sub === ''; + const isServiceToken = !verifyResult.payload.sub; const subject = isServiceToken ? (verifyResult.payload.common_name as string) @@ -271,6 +284,12 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { ); } + if (isServiceToken && !this.serviceTokens.hasOwnProperty(subject)) { + throw new AuthenticationError( + `${subject} is not a permitted Service Token.`, + ); + } + const cacheKey = `${CACHE_PREFIX}/${subject}`; const cfAccessResultStr = await this.cache?.get(cacheKey); if (typeof cfAccessResultStr === 'string') { @@ -289,7 +308,7 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { cfIdentity = { id: subject, name: 'Bot', - email: `${subject}@${this.teamName}.com`, + email: this.serviceTokens[subject], groups: [], }; } else { @@ -372,6 +391,13 @@ export const cfAccess = createAuthProviderIntegration({ }) { return ({ config, resolverContext }) => { const teamName = config.getString('teamName'); + const serviceTokensConfig = config.getOptionalConfig('serviceTokens'); + const serviceTokens: ServiceTokens = {}; + if (serviceTokensConfig) { + serviceTokensConfig.keys().forEach(key => { + serviceTokens[key] = serviceTokensConfig.getString(key); + }); + } if (!options.signIn.resolver) { throw new Error( @@ -393,6 +419,7 @@ export const cfAccess = createAuthProviderIntegration({ return new CloudflareAccessAuthProvider({ teamName, + serviceTokens, signInResolver: options?.signIn.resolver, authHandler, resolverContext, From 7a1c12bb299f37bd606d2e63625dd5c403c437ed Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Wed, 7 Feb 2024 20:30:57 +1100 Subject: [PATCH 3/7] Update docs/auth/cloudflare/access.md typo in docs Signed-off-by: Tyler Davis --- docs/auth/cloudflare/access.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/auth/cloudflare/access.md b/docs/auth/cloudflare/access.md index 457c3abd3e..7c5c5d51d4 100644 --- a/docs/auth/cloudflare/access.md +++ b/docs/auth/cloudflare/access.md @@ -26,7 +26,7 @@ auth: cfaccess: teamName: serviceTokens: - "1uh2fh19efvfh129f1f919u21f2f19jf2.access": "bot-user@your-company.com + "1uh2fh19efvfh129f1f919u21f2f19jf2.access": "bot-user@your-company.com" ``` You can find the team name in the Cloudflare Zero Trust dashboard. From 0d1ad9faf9e6b0006c43c922c0167f5a7521c720 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Thu, 22 Feb 2024 17:20:17 +1100 Subject: [PATCH 4/7] PR feedback: change structure of serviceTokens config Signed-off-by: Tyler Davis --- docs/auth/cloudflare/access.md | 2 +- plugins/auth-backend/config.d.ts | 5 +++ .../providers/cloudflare-access/provider.ts | 32 +++++++++++-------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/docs/auth/cloudflare/access.md b/docs/auth/cloudflare/access.md index 7c5c5d51d4..17a37c6ef8 100644 --- a/docs/auth/cloudflare/access.md +++ b/docs/auth/cloudflare/access.md @@ -26,7 +26,7 @@ auth: cfaccess: teamName: serviceTokens: - "1uh2fh19efvfh129f1f919u21f2f19jf2.access": "bot-user@your-company.com" + '1uh2fh19efvfh129f1f919u21f2f19jf2.access': 'bot-user@your-company.com' ``` You can find the team name in the Cloudflare Zero Trust dashboard. diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 4c8430b33f..0a1425c98b 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -185,6 +185,11 @@ export interface Config { /** @visibility frontend */ cfaccess?: { teamName: string; + /** @visibility secret */ + serviceTokens?: Array<{ + token: string; + subject: string; + }>; }; /** * The backstage token expiration. diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts index 31e6e6d391..e4d317f8f6 100644 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.ts +++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.ts @@ -48,7 +48,10 @@ const CACHE_PREFIX = 'providers/cloudflare-access/profile-v1'; */ export const CF_DEFAULT_CACHE_TTL = 3600; -type ServiceTokens = Record; +type ServiceToken = { + token: string; + subject: string; +}; /** @public */ export type Options = { @@ -68,7 +71,7 @@ export type Options = { * the Client ID of any Service Tokens that should be allowed to pass the * auth check to the identity (email) you would like to associate with it. */ - serviceTokens: ServiceTokens; + serviceTokens: ServiceToken[]; authHandler: AuthHandler; signInResolver: SignInResolver; resolverContext: AuthResolverContext; @@ -189,7 +192,7 @@ export type CloudflareAccessResponse = export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { private readonly teamName: string; - private readonly serviceTokens: ServiceTokens; + private readonly serviceTokens: ServiceToken[]; private readonly resolverContext: AuthResolverContext; private readonly authHandler: AuthHandler; private readonly signInResolver: SignInResolver; @@ -284,7 +287,8 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { ); } - if (isServiceToken && !this.serviceTokens.hasOwnProperty(subject)) { + const serviceToken = this.serviceTokens.find(st => st.token === subject); + if (isServiceToken && !serviceToken) { throw new AuthenticationError( `${subject} is not a permitted Service Token.`, ); @@ -304,11 +308,11 @@ export class CloudflareAccessAuthProvider implements AuthProviderRouteHandlers { // Builds a passport profile from JWT claims first try { let cfIdentity: CloudflareAccessIdentityProfile; - if (isServiceToken) { + if (serviceToken) { cfIdentity = { id: subject, name: 'Bot', - email: this.serviceTokens[subject], + email: serviceToken.subject, groups: [], }; } else { @@ -391,13 +395,15 @@ export const cfAccess = createAuthProviderIntegration({ }) { return ({ config, resolverContext }) => { const teamName = config.getString('teamName'); - const serviceTokensConfig = config.getOptionalConfig('serviceTokens'); - const serviceTokens: ServiceTokens = {}; - if (serviceTokensConfig) { - serviceTokensConfig.keys().forEach(key => { - serviceTokens[key] = serviceTokensConfig.getString(key); - }); - } + const serviceTokensConfig = + config.getOptionalConfigArray('serviceTokens'); + const serviceTokens = + serviceTokensConfig?.map(cfg => { + return { + token: cfg.getString('token'), + subject: cfg.getString('subject'), + } as ServiceToken; + }) || []; if (!options.signIn.resolver) { throw new Error( From d26553df9d9eedee7590ad4768ea42c97e6abc65 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Thu, 22 Feb 2024 17:30:55 +1100 Subject: [PATCH 5/7] update docs Signed-off-by: Tyler Davis --- docs/auth/cloudflare/access.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/auth/cloudflare/access.md b/docs/auth/cloudflare/access.md index 17a37c6ef8..509c0ba052 100644 --- a/docs/auth/cloudflare/access.md +++ b/docs/auth/cloudflare/access.md @@ -26,10 +26,13 @@ auth: cfaccess: teamName: serviceTokens: - '1uh2fh19efvfh129f1f919u21f2f19jf2.access': 'bot-user@your-company.com' + - token: '1uh2fh19efvfh129f1f919u21f2f19jf2.access' + subject: 'bot-user@your-company.com' ``` -You can find the team name in the Cloudflare Zero Trust dashboard. +You can find the team name in the Cloudflare Zero Trust dashboard. The Service +Tokens section is optional -- you only need it if you have some Cloudflare +Service Tokens that you want to be able to log in to your Backstage instance. This config section must be in place for the provider to load at all. Now let's add the provider itself. From bfd0d62351bb028e09547244a64cbd8d59832c00 Mon Sep 17 00:00:00 2001 From: Tyler Davis Date: Thu, 22 Feb 2024 17:31:59 +1100 Subject: [PATCH 6/7] update tests Signed-off-by: Tyler Davis --- .../src/providers/cloudflare-access/provider.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts b/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts index f5f1f361ab..f5f1233ecc 100644 --- a/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts +++ b/plugins/auth-backend/src/providers/cloudflare-access/provider.test.ts @@ -194,7 +194,7 @@ describe('CloudflareAccessAuthProvider', () => { const provider = new CloudflareAccessAuthProvider({ teamName: 'foobar', - serviceTokens: {}, + serviceTokens: [], resolverContext: {} as AuthResolverContext, authHandler: async result => { expect(result).toEqual( @@ -228,9 +228,12 @@ describe('CloudflareAccessAuthProvider', () => { const providerServiceToken = new CloudflareAccessAuthProvider({ teamName: 'foobar', - serviceTokens: { - 'test_token_id.access': 'test_token_id.access@foobar.com', - }, + serviceTokens: [ + { + token: 'test_token_id.access', + subject: 'test_token_id.access@foobar.com', + }, + ], resolverContext: {} as AuthResolverContext, authHandler: async result => { expect(result).toEqual( From 4ba74478473b50e4a1e469cdf5f4e7c8b50268b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 26 Feb 2024 16:38:20 +0100 Subject: [PATCH 7/7] Update plugins/auth-backend/config.d.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- plugins/auth-backend/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 0a1425c98b..f5f4dd29fd 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -185,7 +185,7 @@ export interface Config { /** @visibility frontend */ cfaccess?: { teamName: string; - /** @visibility secret */ + /** @deepVisibility secret */ serviceTokens?: Array<{ token: string; subject: string;