Skip to content

Commit

Permalink
PublicSignupTokens (#2053)
Browse files Browse the repository at this point in the history
* PublicSignupTokens

* bug fix

* bug fixes and test

* bug fixes and test

* bug fixes and test

* Add feature flag

* tests

* tests

* Update 20220908093515-add-public-signup-tokens.js

Bug Fix

* task: use swc instead of ts-jest (#2042)

* Add a counter for total number of environments (#1964)

* add groupId to gradual rollout template (#2045)

* add groupId to gradual rollout template

* FMT

* Improve tabs UI on smaller devices (#2014)

* Improve tabs UI on smaller devices

* Improve tabs UI on smaller devices

* bug fix

* add proper scrollable tabs

* removed centered from Tabs (conflicts with scrollable)

* PR comments

* 4.15.0-beta.10

* Fix broken doc links (#2046)

## What

This PR fixes some broken links that have been hanging around in the
docs for what seems like a very long time.

## Why

As discovered by the link check in #1912, there are a fair few broken
links in the docs. Everyone hates broken links because it makes it
harder to understand what they were supposed to be pointing at.

## How

There are 3 types of links that have been fixed:
- Links that should have been internal but were absolute. E.g.
  `https://docs.getunleash.io/path/article` that should have been
  `./article.md`
- External links that have changed, such as Slack's API description
- GitHub links to files that either no longer exist or that have been
  moved. These links generally pointed to `master`/`main`, meaning
  they are subject to change. They have been replaced with permalinks
  pointing to specific commits.

-----

* docs: fix slack api doc link

* docs: update links in migration guide

* docs: fix broken link to ancient feature schema validation

* docs: update links to v3 auth hooks

* docs: update broken link in the go sdk article

* Fix: use permalink for GitHub link

* docs: fix wrong google auth link

* 4.15.0

* 4.15.1

* docs: update link for symfony sdk (#2048)



The doc link appears to have pointed at an address that is no longer reachable. Instead, let's point to the equivalent GitHub link

Relates to and closes #2047

* docs: test broken links in website (#1912)

The action triggers manually as a first step to test this functionality. In the near future, we might schedule it

* Schedule link checker action (#2050)

Runs at 12:30 UTC on Mon, Tue, Wed, Thu and Fri

* fix: add env and project labels to feature updated metrics. (#2043)

* Revert workflow (#2051)

* update snapshot

* PR comments

* Added Events and tests

* Throw error if token not found

Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai>
Co-authored-by: Thomas Heartman <thomas@getunleash.ai>
Co-authored-by: Gastón Fournier <gaston@getunleash.ai>
Co-authored-by: Ivar Conradi Østhus <ivarconr@gmail.com>
Co-authored-by: sjaanus <sellinjaanus@gmail.com>
  • Loading branch information
6 people committed Sep 14, 2022
1 parent ce3db75 commit 6778d34
Show file tree
Hide file tree
Showing 25 changed files with 1,531 additions and 18 deletions.
6 changes: 6 additions & 0 deletions src/lib/db/index.ts
Expand Up @@ -30,6 +30,7 @@ import UserSplashStore from './user-splash-store';
import RoleStore from './role-store';
import SegmentStore from './segment-store';
import GroupStore from './group-store';
import { PublicSignupTokenStore } from './public-signup-token-store';

export const createStores = (
config: IUnleashConfig,
Expand Down Expand Up @@ -84,6 +85,11 @@ export const createStores = (
roleStore: new RoleStore(db, eventBus, getLogger),
segmentStore: new SegmentStore(db, eventBus, getLogger),
groupStore: new GroupStore(db),
publicSignupTokenStore: new PublicSignupTokenStore(
db,
eventBus,
getLogger,
),
};
};

Expand Down
197 changes: 197 additions & 0 deletions src/lib/db/public-signup-token-store.ts
@@ -0,0 +1,197 @@
import { EventEmitter } from 'events';
import { Knex } from 'knex';
import metricsHelper from '../util/metrics-helper';
import { DB_TIME } from '../metric-events';
import { Logger, LogProvider } from '../logger';
import NotFoundError from '../error/notfound-error';
import { PublicSignupTokenSchema } from '../openapi/spec/public-signup-token-schema';
import { IPublicSignupTokenStore } from '../types/stores/public-signup-token-store';
import { UserSchema } from '../openapi/spec/user-schema';
import { IPublicSignupTokenCreate } from '../types/models/public-signup-token';

const TABLE = 'public_signup_tokens';
const TOKEN_USERS_TABLE = 'public_signup_tokens_user';

interface ITokenInsert {
secret: string;
name: string;
expires_at: Date;
created_at: Date;
created_by?: string;
role_id: number;
}

interface ITokenRow extends ITokenInsert {
users: UserSchema[];
}

interface ITokenUserRow {
secret: string;
user_id: number;
created_at: Date;
}
const tokenRowReducer = (acc, tokenRow) => {
const { userId, name, ...token } = tokenRow;
if (!acc[tokenRow.secret]) {
acc[tokenRow.secret] = {
secret: token.secret,
name: token.name,
expiresAt: token.expires_at,
createdAt: token.created_at,
createdBy: token.created_by,
roleId: token.role_id,
users: [],
};
}
const currentToken = acc[tokenRow.secret];
if (userId) {
currentToken.users.push({ userId, name });
}
return acc;
};

const toRow = (newToken: IPublicSignupTokenCreate) => {
if (!newToken) return;
return {
secret: newToken.secret,
name: newToken.name,
expires_at: newToken.expiresAt,
created_by: newToken.createdBy || null,
role_id: newToken.roleId,
};
};

const toTokens = (rows: any[]): PublicSignupTokenSchema[] => {
const tokens = rows.reduce(tokenRowReducer, {});
return Object.values(tokens);
};

export class PublicSignupTokenStore implements IPublicSignupTokenStore {
private logger: Logger;

private timer: Function;

private db: Knex;

constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) {
this.db = db;
this.logger = getLogger('public-signup-tokens.js');
this.timer = (action: string) =>
metricsHelper.wrapTimer(eventBus, DB_TIME, {
store: 'public-signup-tokens',
action,
});
}

count(): Promise<number> {
return this.db(TABLE)
.count('*')
.then((res) => Number(res[0].count));
}

private makeTokenUsersQuery() {
return this.db<ITokenRow>(`${TABLE} as tokens`)
.leftJoin(
`${TOKEN_USERS_TABLE} as token_project_users`,
'tokens.secret',
'token_project_users.secret',
)
.leftJoin(`users`, 'token_project_users.user_id', 'users.id')
.select(
'tokens.secret',
'tokens.name',
'tokens.expires_at',
'tokens.created_at',
'tokens.created_by',
'tokens.role_id',
'token_project_users.user_id',
'users.name',
);
}

async getAll(): Promise<PublicSignupTokenSchema[]> {
const stopTimer = this.timer('getAll');
const rows = await this.makeTokenUsersQuery();
stopTimer();
return toTokens(rows);
}

async getAllActive(): Promise<PublicSignupTokenSchema[]> {
const stopTimer = this.timer('getAllActive');
const rows = await this.makeTokenUsersQuery()
.where('expires_at', 'IS', null)
.orWhere('expires_at', '>', 'now()');
stopTimer();
return toTokens(rows);
}

async addTokenUser(secret: string, userId: number): Promise<void> {
await this.db<ITokenUserRow>(TOKEN_USERS_TABLE).insert(
{ user_id: userId, secret },
['created_at'],
);
}

async insert(
newToken: IPublicSignupTokenCreate,
): Promise<PublicSignupTokenSchema> {
const response = await this.db<ITokenRow>(TABLE).insert(
toRow(newToken),
['created_at'],
);
return toTokens([response])[0];
}

async isValid(secret: string): Promise<boolean> {
const result = await this.db.raw(
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE secret = ? AND expires_at::date > ?) AS valid`,
[secret, new Date()],
);
const { valid } = result.rows[0];
return valid;
}

destroy(): void {}

async exists(secret: string): Promise<boolean> {
const result = await this.db.raw(
`SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE secret = ?) AS present`,
[secret],
);
const { present } = result.rows[0];
return present;
}

async get(key: string): Promise<PublicSignupTokenSchema> {
const row = await this.makeTokenUsersQuery()
.where('secret', key)
.first();

if (!row)
throw new NotFoundError('Could not find a token with that key');

return toTokens([row])[0];
}

async delete(secret: string): Promise<void> {
return this.db<ITokenInsert>(TABLE).where({ secret }).del();
}

async deleteAll(): Promise<void> {
return this.db<ITokenInsert>(TABLE).del();
}

async setExpiry(
secret: string,
expiresAt: Date,
): Promise<PublicSignupTokenSchema> {
const rows = await this.makeTokenUsersQuery()
.update({ expires_at: expiresAt })
.where('secret', secret)
.returning('*');
if (rows.length > 0) {
return toTokens(rows)[0];
}
throw new NotFoundError('Could not find public signup token.');
}
}
18 changes: 13 additions & 5 deletions src/lib/openapi/index.ts
Expand Up @@ -113,6 +113,10 @@ import { proxyMetricsSchema } from './spec/proxy-metrics-schema';
import { setUiConfigSchema } from './spec/set-ui-config-schema';
import { edgeTokenSchema } from './spec/edge-token-schema';
import { validateEdgeTokensSchema } from './spec/validate-edge-tokens-schema';
import { publicSignupTokenCreateSchema } from './spec/public-signup-token-create-schema';
import { publicSignupTokenSchema } from './spec/public-signup-token-schema';
import { publicSignupTokensSchema } from './spec/public-signup-tokens-schema';
import { publicSignupTokenUpdateSchema } from './spec/public-signup-token-update-schema';

// All schemas in `openapi/spec` should be listed here.
export const schemas = {
Expand Down Expand Up @@ -140,6 +144,7 @@ export const schemas = {
createUserSchema,
dateSchema,
emailSchema,
edgeTokenSchema,
environmentSchema,
environmentsSchema,
eventSchema,
Expand Down Expand Up @@ -182,6 +187,14 @@ export const schemas = {
playgroundRequestSchema,
playgroundResponseSchema,
projectEnvironmentSchema,
publicSignupTokenCreateSchema,
publicSignupTokenUpdateSchema,
publicSignupTokensSchema,
publicSignupTokenSchema,
proxyClientSchema,
proxyFeaturesSchema,
proxyFeatureSchema,
proxyMetricsSchema,
projectSchema,
projectsSchema,
resetPasswordSchema,
Expand Down Expand Up @@ -219,11 +232,6 @@ export const schemas = {
variantSchema,
variantsSchema,
versionSchema,
proxyClientSchema,
proxyFeaturesSchema,
proxyFeatureSchema,
proxyMetricsSchema,
edgeTokenSchema,
validateEdgeTokensSchema,
};

Expand Down
22 changes: 22 additions & 0 deletions src/lib/openapi/spec/public-signup-schema.test.ts
@@ -0,0 +1,22 @@
import { validateSchema } from '../validate';
import { PublicSignupTokenSchema } from './public-signup-token-schema';

test('publicSignupTokenSchema', () => {
const data: PublicSignupTokenSchema = {
name: 'Default',
secret: 'some-secret',
expiresAt: new Date().toISOString(),
users: [],
role: { name: 'Viewer ', type: 'type', id: 1 },
createdAt: new Date().toISOString(),
createdBy: 'someone',
};

expect(
validateSchema('#/components/schemas/publicSignupTokenSchema', {}),
).not.toBeUndefined();

expect(
validateSchema('#/components/schemas/publicSignupTokenSchema', data),
).toBeUndefined();
});
22 changes: 22 additions & 0 deletions src/lib/openapi/spec/public-signup-token-create-schema.ts
@@ -0,0 +1,22 @@
import { FromSchema } from 'json-schema-to-ts';

export const publicSignupTokenCreateSchema = {
$id: '#/components/schemas/publicSignupTokenCreateSchema',
type: 'object',
additionalProperties: false,
required: ['name', 'expiresAt'],
properties: {
name: {
type: 'string',
},
expiresAt: {
type: 'string',
format: 'date-time',
},
},
components: {},
} as const;

export type PublicSignupTokenCreateSchema = FromSchema<
typeof publicSignupTokenCreateSchema
>;
50 changes: 50 additions & 0 deletions src/lib/openapi/spec/public-signup-token-schema.ts
@@ -0,0 +1,50 @@
import { FromSchema } from 'json-schema-to-ts';
import { userSchema } from './user-schema';
import { roleSchema } from './role-schema';

export const publicSignupTokenSchema = {
$id: '#/components/schemas/publicSignupTokenSchema',
type: 'object',
additionalProperties: false,
required: ['secret', 'name', 'expiresAt', 'createdAt', 'createdBy', 'role'],
properties: {
secret: {
type: 'string',
},
name: {
type: 'string',
},
expiresAt: {
type: 'string',
format: 'date-time',
},
createdAt: {
type: 'string',
format: 'date-time',
},
createdBy: {
type: 'string',
nullable: true,
},
users: {
type: 'array',
items: {
$ref: '#/components/schemas/userSchema',
},
nullable: true,
},
role: {
$ref: '#/components/schemas/roleSchema',
},
},
components: {
schemas: {
userSchema,
roleSchema,
},
},
} as const;

export type PublicSignupTokenSchema = FromSchema<
typeof publicSignupTokenSchema
>;
19 changes: 19 additions & 0 deletions src/lib/openapi/spec/public-signup-token-update-schema.ts
@@ -0,0 +1,19 @@
import { FromSchema } from 'json-schema-to-ts';

export const publicSignupTokenUpdateSchema = {
$id: '#/components/schemas/publicSignupTokenUpdateSchema',
type: 'object',
additionalProperties: false,
required: ['expiresAt'],
properties: {
expiresAt: {
type: 'string',
format: 'date-time',
},
},
components: {},
} as const;

export type PublicSignupTokenUpdateSchema = FromSchema<
typeof publicSignupTokenUpdateSchema
>;

0 comments on commit 6778d34

Please sign in to comment.