Skip to content

Commit

Permalink
feat: add support for signing keys APIs (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
tylerburdsall authored Apr 28, 2022
1 parent e5bc786 commit ab98bd7
Show file tree
Hide file tree
Showing 10 changed files with 709 additions and 527 deletions.
20 changes: 20 additions & 0 deletions integration/SimpleCacheClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,4 +193,24 @@ describe('SimpleCacheClient.ts Integration Tests', () => {
);
await momento.deleteCache(cacheName);
});
it('should create, list, and revoke a signing key', async () => {
const momento = new SimpleCacheClient(AUTH_TOKEN, 1111);
const createSigningKeyResponse = await momento.createSigningKey(30);
let listSigningKeysResponse = await momento.listSigningKeys();
let signingKeys = listSigningKeysResponse.getSigningKeys();
expect(signingKeys.length).toBeGreaterThan(0);
expect(
signingKeys
.map(k => k.getKeyId())
.some(k => k === createSigningKeyResponse.getKeyId())
).toEqual(true);
await momento.revokeSigningKey(createSigningKeyResponse.getKeyId());
listSigningKeysResponse = await momento.listSigningKeys();
signingKeys = listSigningKeysResponse.getSigningKeys();
expect(
signingKeys
.map(k => k.getKeyId())
.some(k => k === createSigningKeyResponse.getKeyId())
).toEqual(false);
});
});
999 changes: 475 additions & 524 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@grpc/grpc-js": "1.5.10",
"jwt-decode": "3.1.2",
"toml": "^3.0.0",
"@gomomento/generated-types": "0.10.1"
"@gomomento/generated-types": "0.12.0"
},
"engines": {
"node": ">= 10.13.0"
Expand Down
76 changes: 74 additions & 2 deletions src/Momento.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {DeleteCacheResponse} from './messages/DeleteCacheResponse';
import {CreateCacheResponse} from './messages/CreateCacheResponse';
import {ListCachesResponse} from './messages/ListCachesResponse';
import {version} from '../package.json';
import {CreateSigningKeyResponse} from './messages/CreateSigningKeyResponse';
import {RevokeSigningKeyResponse} from './messages/RevokeSigningKeyResponse';
import {ListSigningKeysResponse} from './messages/ListSigningKeysResponse';

export interface MomentoProps {
authToken: string;
Expand Down Expand Up @@ -115,9 +118,78 @@ export class Momento {
});
}

private validateCacheName = (name: string) => {
public async createSigningKey(
ttlMinutes: number,
endpoint: string
): Promise<CreateSigningKeyResponse> {
this.validateTtlMinutes(ttlMinutes);
const request = new control.control_client._CreateSigningKeyRequest();
request.ttl_minutes = ttlMinutes;
return await new Promise<CreateSigningKeyResponse>((resolve, reject) => {
this.client.CreateSigningKey(
request,
{interceptors: this.interceptors},
(err, resp) => {
if (err) {
reject(cacheServiceErrorMapper(err));
} else {
resolve(new CreateSigningKeyResponse(endpoint, resp));
}
}
);
});
}

public async revokeSigningKey(
keyId: string
): Promise<RevokeSigningKeyResponse> {
const request = new control.control_client._RevokeSigningKeyRequest();
request.key_id = keyId;
return await new Promise<RevokeSigningKeyResponse>((resolve, reject) => {
this.client.RevokeSigningKey(
request,
{interceptors: this.interceptors},
err => {
if (err) {
reject(cacheServiceErrorMapper(err));
} else {
resolve(new RevokeSigningKeyResponse());
}
}
);
});
}

public async listSigningKeys(
endpoint: string,
nextToken?: string
): Promise<ListSigningKeysResponse> {
const request = new control.control_client._ListSigningKeysRequest();
request.next_token = nextToken ?? '';
return await new Promise<ListSigningKeysResponse>((resolve, reject) => {
this.client.ListSigningKeys(
request,
{interceptors: this.interceptors},
(err, resp) => {
if (err) {
reject(cacheServiceErrorMapper(err));
} else {
resolve(new ListSigningKeysResponse(endpoint, resp));
}
}
);
});
}

private validateCacheName(name: string) {
if (!name.trim()) {
throw new InvalidArgumentError('cache name must not be empty');
}
};
}

private validateTtlMinutes(ttlMinutes: number) {
if (ttlMinutes < 0) {
throw new InvalidArgumentError('ttlMinutes must be positive');
}
}
}
6 changes: 6 additions & 0 deletions src/MomentoCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class MomentoCache {
private readonly defaultTtlSeconds: number;
private readonly requestTimeoutMs: number;
private readonly authToken: string;
private readonly endpoint: string;
private static readonly DEFAULT_REQUEST_TIMEOUT_MS: number = 5 * 1000;
private static isUserAgentSent = false;

Expand All @@ -47,6 +48,11 @@ export class MomentoCache {
this.requestTimeoutMs =
props.requestTimeoutMs || MomentoCache.DEFAULT_REQUEST_TIMEOUT_MS;
this.authToken = props.authToken;
this.endpoint = props.endpoint;
}

public getEndpoint(): string {
return this.endpoint;
}

private static validateRequestTimeout(timeout?: number) {
Expand Down
42 changes: 42 additions & 0 deletions src/SimpleCacheClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {GetResponse} from './messages/GetResponse';
import {CreateCacheResponse} from './messages/CreateCacheResponse';
import {DeleteCacheResponse} from './messages/DeleteCacheResponse';
import {ListCachesResponse} from './messages/ListCachesResponse';
import {CreateSigningKeyResponse} from './messages/CreateSigningKeyResponse';
import {RevokeSigningKeyResponse} from './messages/RevokeSigningKeyResponse';
import {ListSigningKeysResponse} from './messages/ListSigningKeysResponse';

export class SimpleCacheClient {
private readonly dataClient: MomentoCache;
Expand Down Expand Up @@ -92,4 +95,43 @@ export class SimpleCacheClient {
public async listCaches(nextToken?: string): Promise<ListCachesResponse> {
return await this.controlClient.listCaches(nextToken);
}

/**
* creates a Momento signing key
* @param ttlMinutes - the time to live in minutes until the Momento signing key expires
* @returns Promise<CreateSigningKeyResponse>
*/
public async createSigningKey(
ttlMinutes: number
): Promise<CreateSigningKeyResponse> {
return await this.controlClient.createSigningKey(
ttlMinutes,
this.dataClient.getEndpoint()
);
}

/**
* revokes a Momento signing key, all tokens signed by which will be invalid
* @param keyId - the id of the Momento signing key to revoke
* @returns Promise<RevokeSigningKeyResponse>
*/
public async revokeSigningKey(
keyId: string
): Promise<RevokeSigningKeyResponse> {
return await this.controlClient.revokeSigningKey(keyId);
}

/**
* lists all Momento signing keys for the provided auth token
* @param nextToken - token to continue paginating through the list. It's used to handle large paginated lists.
* @returns Promise<ListSigningKeysResponse>
*/
public async listSigningKeys(
nextToken?: string
): Promise<ListSigningKeysResponse> {
return await this.controlClient.listSigningKeys(
this.dataClient.getEndpoint(),
nextToken
);
}
}
36 changes: 36 additions & 0 deletions src/messages/CreateSigningKeyResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {control} from '@gomomento/generated-types';

export class CreateSigningKeyResponse {
private readonly keyId: string;
private readonly endpoint: string;
private readonly key: string;
private readonly expiresAt: Date;

constructor(
endpoint: string,
result?: control.control_client._CreateSigningKeyResponse
) {
const key = result?.key ?? '';
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
this.keyId = JSON.parse(key)['kid'];
this.endpoint = endpoint;
this.key = key;
this.expiresAt = new Date(result?.expires_at ?? 0 * 1000);
}

public getKeyId(): string {
return this.keyId;
}

public getEndpoint(): string {
return this.endpoint;
}

public getKey(): string {
return this.key;
}

public getExpiresAt(): Date {
return this.expiresAt;
}
}
31 changes: 31 additions & 0 deletions src/messages/ListSigningKeysResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {control} from '@gomomento/generated-types';
import {SigningKey} from './SigningKey';

export class ListSigningKeysResponse {
private readonly nextToken: string | null;
private readonly signingKeys: SigningKey[];

constructor(
endpoint: string,
result?: control.control_client._ListSigningKeysResponse
) {
this.nextToken = result?.next_token || null;
this.signingKeys =
result?.signing_key.map(
signingKey =>
new SigningKey(
signingKey.key_id,
new Date(signingKey.expires_at * 1000),
endpoint
)
) ?? [];
}

public getNextToken() {
return this.nextToken;
}

public getSigningKeys() {
return this.signingKeys;
}
}
1 change: 1 addition & 0 deletions src/messages/RevokeSigningKeyResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class RevokeSigningKeyResponse {}
23 changes: 23 additions & 0 deletions src/messages/SigningKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export class SigningKey {
private readonly keyId: string;
private readonly expiresAt: Date;
private readonly endpoint: string;

constructor(keyId: string, expiresAt: Date, endpoint: string) {
this.keyId = keyId;
this.expiresAt = expiresAt;
this.endpoint = endpoint;
}

public getKeyId() {
return this.keyId;
}

public getExpiresAt() {
return this.expiresAt;
}

public getEndpoint() {
return this.endpoint;
}
}

0 comments on commit ab98bd7

Please sign in to comment.