Skip to content

Commit f178db7

Browse files
committed
add delete/get key to cli
1 parent 14b9d66 commit f178db7

File tree

6 files changed

+88
-91
lines changed

6 files changed

+88
-91
lines changed

cli/src/apiToken.ts

Lines changed: 9 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,13 @@
1-
import { getAccessToken } from "./accessToken.ts";
1+
import { callServer } from "./rpc.ts";
22

3-
const serverURL = Deno.env.get("RMMBR_SERVER");
3+
const list = callServer("api-token/", "GET", undefined);
4+
const create = callServer("api-token/", "POST", { action: "create" });
45

5-
type APITokenInterface =
6-
| { create: true }
7-
| { delete: string }
8-
| { list: true }
9-
| { get: true };
10-
11-
export const apiToken = (
12-
cmd: APITokenInterface,
13-
) => {
14-
const [action, args] =
15-
Object.entries(cmd).find(([action]) => action in commandMapping) ||
16-
Deno.exit();
17-
return getAccessToken().then(commandMapping[action](args));
18-
};
19-
20-
const apiTokenRequest = (
21-
method: "GET" | "POST",
22-
body: undefined | Record<string, string>,
23-
) =>
24-
(accessToken: string) =>
25-
fetch(`${serverURL}/api-token/`, {
26-
headers: { Authorization: `Bearer ${accessToken}` },
27-
method,
28-
body: JSON.stringify(body),
29-
}).then(
30-
async (response) =>
31-
response.status === 200
32-
? response.json()
33-
: Promise.reject(await response.text()),
34-
);
35-
36-
const createApiToken = apiTokenRequest("POST", { action: "create" });
37-
const listApiTokens = apiTokenRequest("GET", undefined);
38-
39-
const getOrCreateApiToken = (
40-
accessToken: string,
41-
): Promise<string> =>
42-
listApiTokens(accessToken).then(
43-
(tokens) => tokens.length == 0 ? createApiToken(accessToken) : tokens[0],
44-
);
45-
46-
const commandMapping: Record<
47-
string,
48-
((..._: string[]) => (_: string) => Promise<string>)
49-
> = {
50-
create: () => createApiToken,
6+
export const apiTokenCommands = {
7+
create,
8+
list,
519
delete: (tokenId: string) =>
52-
apiTokenRequest("POST", { action: "delete", tokenId }),
53-
list: () => listApiTokens,
54-
get: () => getOrCreateApiToken,
10+
callServer("api-token/", "POST", { action: "delete", tokenId }),
11+
get: (secret: string) =>
12+
list(secret).then((tokens: string[]) => tokens[0] || create(secret)),
5513
};

cli/src/index.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { apiToken } from "./apiToken.ts";
2-
import { keyManipulation } from "./keyManipulation.ts";
1+
import { apiTokenCommands } from "./apiToken.ts";
2+
import { getAccessToken } from "./accessToken.ts";
3+
import { keyManipulations } from "./keyManipulation.ts";
34
import { login } from "./login.ts";
45
import { randomBytes } from "node:crypto";
56
import yargs from "https://deno.land/x/yargs@v17.7.2-deno/deno.ts";
67

78
const args = yargs(Deno.args)
89
.scriptName("rmmbr")
910
.command("login", "Authenticate the CLI")
11+
// deno-lint-ignore no-explicit-any
1012
.command("key", "Manage cache keys", (yargs: any) =>
1113
yargs
1214
.option("get", {
@@ -23,10 +25,11 @@ const args = yargs(Deno.args)
2325
.command(
2426
"token",
2527
"Manage API tokens",
28+
// deno-lint-ignore no-explicit-any
2629
(yargs: any) =>
2730
yargs.option("get", {
2831
alias: "g",
29-
description: "(Default) Get or create the first API token",
32+
description: "Get or create the first API token",
3033
boolean: true,
3134
}).option(
3235
"create",
@@ -60,15 +63,29 @@ const args = yargs(Deno.args)
6063
.version("0.1")
6164
.parse();
6265

63-
const command = args._[0];
64-
const commands: Record<string, () => Promise<string>> = {
66+
type YargsInstance = ReturnType<typeof yargs>;
67+
type Handler = (
68+
// deno-lint-ignore no-explicit-any
69+
cmdInput: any,
70+
) => (accessToken: string) => Promise<string>;
71+
72+
const pickAndRunCommand =
73+
(availableCmds: Record<string, Handler>) => (cmdInput: YargsInstance) => {
74+
const [action, args] =
75+
Object.entries(cmdInput).find(([action]) => action in availableCmds) ||
76+
Deno.exit();
77+
return getAccessToken().then(availableCmds[action](args));
78+
};
79+
80+
// deno-lint-ignore no-explicit-any
81+
const commands: Record<string, (args: any) => Promise<string>> = {
6582
login,
66-
key: () => keyManipulation(args),
67-
token: () => apiToken(args),
83+
key: pickAndRunCommand(keyManipulations),
84+
token: pickAndRunCommand(apiTokenCommands),
6885
secret: () => Promise.resolve(randomBytes(32).toString("base64url") + "="),
6986
};
7087

71-
commands[command]().then(console.log).catch((msg) => {
88+
commands[args._[0]](args).then(console.log).catch((msg) => {
7289
console.error(msg);
7390
Deno.exit(1);
7491
});

cli/src/keyManipulation.ts

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
1-
import { getAccessToken } from "./accessToken.ts";
1+
import { callServer } from "./rpc.ts";
2+
import { inputToCacheKey } from "../../client/src/index.ts";
23

3-
const commandMapping: Record<
4-
string,
5-
((..._: string[]) => (_: string) => Promise<string>)
6-
> = {
7-
delete: (apiToken: string) => (secretAndKey: string) =>
8-
Promise.resolve("not yet implemented"),
9-
get: (apiToken: string) => (secretAndKey: string) =>
10-
Promise.resolve("not yet implemented"),
4+
const parseCacheIdAndKey = (hashKey: (...xs: any[]) => string, x: string) => {
5+
const [cacheId, key] = x.split(":");
6+
return { cacheId, key: hashKey(...JSON.parse(key)) };
117
};
8+
const actOnKey = (action: string) => (args: string) => (secret: string) =>
9+
callServer("", "GET", {
10+
action,
11+
...parseCacheIdAndKey(inputToCacheKey(secret, undefined), args),
12+
})(
13+
secret,
14+
);
1215

13-
type APITokenInterface =
14-
| { delete: string }
15-
| { get: true };
16-
17-
export const keyManipulation = (cmd: APITokenInterface) => {
18-
const [action, args] =
19-
Object.entries(cmd).find(([action]) => action in commandMapping) ||
20-
Deno.exit();
21-
return getAccessToken()
22-
.then(commandMapping[action](args));
16+
export const keyManipulations = {
17+
delete: actOnKey("delete"),
18+
get: actOnKey("get"),
2319
};

cli/src/rpc.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const serverURL = Deno.env.get("RMMBR_SERVER");
2+
3+
export const callServer = (
4+
path: string,
5+
method: "GET" | "POST",
6+
body: undefined | Record<string, string>,
7+
) =>
8+
(accessToken: string) =>
9+
fetch(`${serverURL}/${path}`, {
10+
headers: { Authorization: `Bearer ${accessToken}` },
11+
method,
12+
body: JSON.stringify(body),
13+
}).then(
14+
async (response) =>
15+
response.status === 200
16+
? response.json()
17+
: Promise.reject(await response.text()),
18+
);

client/src/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const waitAllWrites = () => Promise.all(writePromises);
9292

9393
type CustomKeyFn = (..._: any[]) => any;
9494

95-
const inputToCacheKey =
95+
export const inputToCacheKey =
9696
// deno-lint-ignore no-explicit-any
9797
<Args extends any[]>(secret: string, customKeyFn: CustomKeyFn | undefined) =>
9898
(...x: Args): string =>
@@ -110,12 +110,12 @@ export const memCache =
110110
// @ts-expect-error Promise<Awaited<Awaited<X>>> is just Promise<X>
111111
read: (key: string) => {
112112
if (!(key in keyToValue)) {
113-
return Promise.reject();
113+
return Promise.reject(new Error());
114114
}
115115
if (ttl && Date.now() - keyToTimestamp[key] > ttl * 1000) {
116116
delete keyToTimestamp[key];
117117
delete keyToValue[key];
118-
return Promise.reject();
118+
return Promise.reject(new Error());
119119
}
120120
return Promise.resolve(keyToValue[key]);
121121
},
@@ -217,17 +217,17 @@ const cloudCache = (params: CloudCacheParams) => <F extends Func>(f: F) =>
217217
.then((value) =>
218218
value
219219
? params.encryptionKey
220-
? decrypt(params.encryptionKey as string)(value).then(
220+
? decrypt(params.encryptionKey!)(value).then(
221221
JSON.parse,
222222
)
223223
: value
224-
: Promise.reject()
224+
: Promise.reject(new Error())
225225
) as ReturnType<F>,
226226
write: params.encryptionKey
227227
? async (key, value) =>
228228
setRemote(params)(
229229
key,
230-
await encrypt(params.encryptionKey as string)(
230+
await encrypt(params.encryptionKey!)(
231231
jsonStableStringify(value),
232232
),
233233
)

server/src/index.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ const verifyAuth0 = (token: string): Promise<string | null> =>
3131
audience: "rmmbr",
3232
}).then((x) => x.payload.sub || null).catch(() => null);
3333

34-
type CREATE_TOKEN = { action: "create" };
35-
type DELETE_TOKEN = { action: "delete"; tokenId: string };
34+
type ApiTokenAction = { action: "create" } | {
35+
action: "delete";
36+
tokenId: string;
37+
};
3638

3739
serve(
3840
app({
@@ -41,17 +43,22 @@ serve(
4143
memCache({ ttl: 60 * 5 })(verifyApiToken),
4244
async (request, uid) => {
4345
const { method, params } = await request.json();
46+
console.log("incoming request", method, params);
4447
if (method === "get") {
4548
const { cacheId, key } = params;
46-
console.log(`incoming get request for key ${key}`);
4749
return new Response(
4850
(await redisClient.get(`${uid}:${cacheId}:${key}`)) ||
4951
JSON.stringify(null),
5052
);
5153
}
54+
if (method === "delete") {
55+
const { cacheId, key } = params;
56+
return new Response(
57+
JSON.stringify(await redisClient.del(`${uid}:${cacheId}:${key}`)),
58+
);
59+
}
5260
if (method === "set") {
5361
const { cacheId, key, value, ttl } = params;
54-
console.log(`incoming set request for key ${key}`);
5562
await redisClient.set(
5663
`${uid}:${cacheId}:${key}`,
5764
JSON.stringify(value),
@@ -77,16 +84,17 @@ serve(
7784
verifyAuth0,
7885
(request, uid) =>
7986
request.json().then(
80-
(call: CREATE_TOKEN | DELETE_TOKEN) => {
81-
if (call.action == "create") {
87+
(call: ApiTokenAction) => {
88+
if (call.action === "create") {
8289
const token = crypto.randomUUID();
8390
const tx = redisClient.tx();
8491
tx.rpush(redisKey.userToApiTokenSet(uid), token);
8592
tx.set(redisKey.apiTokenToUser(token), uid);
8693
return tx.flush().then(() =>
8794
new Response(JSON.stringify(token))
8895
);
89-
} else if (call.action == "delete") {
96+
}
97+
if (call.action === "delete") {
9098
return getApiTokens(uid).then((tokens) => {
9199
const tokensToDelete = tokens.filter((t) =>
92100
t.startsWith(call.tokenId)

0 commit comments

Comments
 (0)