1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00

feat: Remove orphaned tokens flags (#7714)

Cleanup of `allowOrphanedWildcardTokens` and `cleanApiTokenWhenOrphaned`
This commit is contained in:
Tymoteusz Czech 2024-08-01 13:31:52 +02:00 committed by GitHub
parent 5668bfb7d4
commit d1e70eefbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 48 additions and 111 deletions

View File

@ -76,14 +76,12 @@ exports[`should create default config 1`] = `
"flagResolver": FlagResolver { "flagResolver": FlagResolver {
"experiments": { "experiments": {
"adminTokenKillSwitch": false, "adminTokenKillSwitch": false,
"allowOrphanedWildcardTokens": true,
"anonymiseEventLog": false, "anonymiseEventLog": false,
"anonymizeProjectOwners": false, "anonymizeProjectOwners": false,
"automatedActions": false, "automatedActions": false,
"caseInsensitiveInOperators": false, "caseInsensitiveInOperators": false,
"celebrateUnleash": false, "celebrateUnleash": false,
"changeRequestPlayground": false, "changeRequestPlayground": false,
"cleanApiTokenWhenOrphaned": false,
"collectTrafficDataUsage": false, "collectTrafficDataUsage": false,
"commandBarUI": false, "commandBarUI": false,
"demo": false, "demo": false,

View File

@ -36,45 +36,33 @@ interface ITokenRow extends ITokenInsert {
project: string; project: string;
} }
const createTokenRowReducer = const tokenRowReducer = (acc, tokenRow) => {
(allowOrphanedWildcardTokens: boolean) => (acc, tokenRow) => { const { project, ...token } = tokenRow;
const { project, ...token } = tokenRow; if (!acc[tokenRow.secret]) {
if (!acc[tokenRow.secret]) { acc[tokenRow.secret] = {
if ( secret: token.secret,
!allowOrphanedWildcardTokens && tokenName: token.token_name ? token.token_name : token.username,
!tokenRow.project && type: token.type.toLowerCase(),
tokenRow.secret.includes(':') && // Exclude v1 tokens project: ALL,
!tokenRow.secret.startsWith('*:') && // Exclude intentionally wildcard projects: [ALL],
(tokenRow.type === ApiTokenType.CLIENT || environment: token.environment ? token.environment : ALL,
tokenRow.type === ApiTokenType.FRONTEND) expiresAt: token.expires_at,
) { createdAt: token.created_at,
return acc; alias: token.alias,
} seenAt: token.seen_at,
username: token.token_name ? token.token_name : token.username,
acc[tokenRow.secret] = { };
secret: token.secret, }
tokenName: token.token_name ? token.token_name : token.username, const currentToken = acc[tokenRow.secret];
type: token.type.toLowerCase(), if (tokenRow.project) {
project: ALL, if (isAllProjects(currentToken.projects)) {
projects: [ALL], currentToken.projects = [];
environment: token.environment ? token.environment : ALL,
expiresAt: token.expires_at,
createdAt: token.created_at,
alias: token.alias,
seenAt: token.seen_at,
username: token.token_name ? token.token_name : token.username,
};
} }
const currentToken = acc[tokenRow.secret]; currentToken.projects.push(tokenRow.project);
if (tokenRow.project) { currentToken.project = currentToken.projects.join(',');
if (isAllProjects(currentToken.projects)) { }
currentToken.projects = []; return acc;
} };
currentToken.projects.push(tokenRow.project);
currentToken.project = currentToken.projects.join(',');
}
return acc;
};
const toRow = (newToken: IApiTokenCreate) => ({ const toRow = (newToken: IApiTokenCreate) => ({
username: newToken.tokenName ?? newToken.username, username: newToken.tokenName ?? newToken.username,
@ -87,14 +75,8 @@ const toRow = (newToken: IApiTokenCreate) => ({
alias: newToken.alias || null, alias: newToken.alias || null,
}); });
const toTokens = ( const toTokens = (rows: any[]): IApiToken[] => {
rows: any[], const tokens = rows.reduce(tokenRowReducer, {});
allowOrphanedWildcardTokens: boolean,
): IApiToken[] => {
const tokens = rows.reduce(
createTokenRowReducer(allowOrphanedWildcardTokens),
{},
);
return Object.values(tokens); return Object.values(tokens);
}; };
@ -147,10 +129,7 @@ export class ApiTokenStore implements IApiTokenStore {
const stopTimer = this.timer('getAll'); const stopTimer = this.timer('getAll');
const rows = await this.makeTokenProjectQuery(); const rows = await this.makeTokenProjectQuery();
stopTimer(); stopTimer();
const allowOrphanedWildcardTokens = this.flagResolver.isEnabled( return toTokens(rows);
'allowOrphanedWildcardTokens',
);
return toTokens(rows, allowOrphanedWildcardTokens);
} }
async getAllActive(): Promise<IApiToken[]> { async getAllActive(): Promise<IApiToken[]> {
@ -159,10 +138,7 @@ export class ApiTokenStore implements IApiTokenStore {
.where('expires_at', 'IS', null) .where('expires_at', 'IS', null)
.orWhere('expires_at', '>', 'now()'); .orWhere('expires_at', '>', 'now()');
stopTimer(); stopTimer();
const allowOrphanedWildcardTokens = this.flagResolver.isEnabled( return toTokens(rows);
'allowOrphanedWildcardTokens',
);
return toTokens(rows, allowOrphanedWildcardTokens);
} }
private makeTokenProjectQuery() { private makeTokenProjectQuery() {
@ -233,10 +209,7 @@ export class ApiTokenStore implements IApiTokenStore {
key, key,
); );
stopTimer(); stopTimer();
const allowOrphanedWildcardTokens = this.flagResolver.isEnabled( return toTokens(row)[0];
'allowOrphanedWildcardTokens',
);
return toTokens(row, allowOrphanedWildcardTokens)[0];
} }
async delete(secret: string): Promise<void> { async delete(secret: string): Promise<void> {
@ -253,10 +226,7 @@ export class ApiTokenStore implements IApiTokenStore {
.where({ secret }) .where({ secret })
.returning('*'); .returning('*');
if (rows.length > 0) { if (rows.length > 0) {
const allowOrphanedWildcardTokens = this.flagResolver.isEnabled( return toTokens(rows)[0];
'allowOrphanedWildcardTokens',
);
return toTokens(rows, allowOrphanedWildcardTokens)[0];
} }
throw new NotFoundError('Could not find api-token.'); throw new NotFoundError('Could not find api-token.');
} }

View File

@ -79,14 +79,7 @@ beforeAll(async () => {
email: 'test@example.com', email: 'test@example.com',
}); });
await stores.accessStore.addUserToRole(opsUser.id, 1, ''); await stores.accessStore.addUserToRole(opsUser.id, 1, '');
const config = createTestConfig({ const config = createTestConfig({ getLogger });
getLogger,
experimental: {
flags: {
cleanApiTokenWhenOrphaned: true,
},
},
});
eventService = new EventService(stores, config); eventService = new EventService(stores, config);
accessService = createAccessService(db.rawDatabase, config); accessService = createAccessService(db.rawDatabase, config);

View File

@ -564,26 +564,22 @@ export default class ProjectService {
auditUser, auditUser,
); );
if (this.flagResolver.isEnabled('cleanApiTokenWhenOrphaned')) { const allTokens = await this.apiTokenService.getAllTokens();
const allTokens = await this.apiTokenService.getAllTokens(); const projectTokens = allTokens.filter(
const projectTokens = allTokens.filter( (token) =>
(token) => (token.projects &&
(token.projects && token.projects.length === 1 &&
token.projects.length === 1 && token.projects[0] === id) ||
token.projects[0] === id) || token.project === id,
token.project === id, );
);
await this.projectStore.delete(id); await this.projectStore.delete(id);
await Promise.all( await Promise.all(
projectTokens.map((token) => projectTokens.map((token) =>
this.apiTokenService.delete(token.secret, auditUser), this.apiTokenService.delete(token.secret, auditUser),
), ),
); );
} else {
await this.projectStore.delete(id);
}
await this.eventService.storeEvent( await this.eventService.storeEvent(
new ProjectDeletedEvent({ new ProjectDeletedEvent({

View File

@ -64,8 +64,6 @@ export type IFlagKey =
| 'anonymizeProjectOwners' | 'anonymizeProjectOwners'
| 'resourceLimits' | 'resourceLimits'
| 'extendedMetrics' | 'extendedMetrics'
| 'cleanApiTokenWhenOrphaned'
| 'allowOrphanedWildcardTokens'
| 'removeUnsafeInlineStyleSrc' | 'removeUnsafeInlineStyleSrc'
| 'insightsV2' | 'insightsV2'
| 'integrationEvents' | 'integrationEvents'
@ -310,18 +308,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_RESOURCE_LIMITS, process.env.UNLEASH_EXPERIMENTAL_RESOURCE_LIMITS,
false, false,
), ),
allowOrphanedWildcardTokens: parseEnvVarBoolean(
process.env.UNLEASH_ORPHANED_TOKENS_KILL_SWITCH,
true,
),
extendedMetrics: parseEnvVarBoolean( extendedMetrics: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_EXTENDED_METRICS, process.env.UNLEASH_EXPERIMENTAL_EXTENDED_METRICS,
false, false,
), ),
cleanApiTokenWhenOrphaned: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_CLEAN_API_TOKEN_WHEN_ORPHANED,
false,
),
removeUnsafeInlineStyleSrc: parseEnvVarBoolean( removeUnsafeInlineStyleSrc: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_REMOVE_UNSAFE_INLINE_STYLE_SRC, process.env.UNLEASH_EXPERIMENTAL_REMOVE_UNSAFE_INLINE_STYLE_SRC,
false, false,

View File

@ -23,17 +23,7 @@ const feature3 = 'f3.p2.token.access';
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_api_api_access_client_deletion', getLogger); db = await dbInit('feature_api_api_access_client_deletion', getLogger);
app = await setupAppWithAuth( app = await setupAppWithAuth(db.stores, {}, db.rawDatabase);
db.stores,
{
experimental: {
flags: {
cleanApiTokenWhenOrphaned: true,
},
},
},
db.rawDatabase,
);
apiTokenService = app.services.apiTokenService; apiTokenService = app.services.apiTokenService;
const { featureToggleServiceV2, environmentService } = app.services; const { featureToggleServiceV2, environmentService } = app.services;