Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

feat(tokens): Add script to extend token life #73

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions modules/tokens/module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ scripts:
validate:
name: Validate Token
description: Validate a token. Throws an error if the token is invalid.
extend:
name: Extend Token
description: Extend or remove the expiration date of a token. (Only works on valid tokens.)
errors:
token_not_found:
name: Token Not Found
Expand Down
37 changes: 37 additions & 0 deletions modules/tokens/scripts/extend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ScriptContext } from "../_gen/scripts/extend.ts";
import { TokenWithSecret } from "../types/common.ts";
import { tokenFromRow } from "../types/common.ts";

export interface Request {
token: string;
newExpiration: string | null;
}

export interface Response {
token: TokenWithSecret;
}

export async function run(
ctx: ScriptContext,
req: Request,
): Promise<Response> {
// Ensure the token hasn't expired or been revoked yet
const { token } = await ctx.modules.tokens.validate({
token: req.token,
});

// Update the token's expiration date
const newToken = await ctx.db.token.update({
where: {
id: token.id,
},
data: {
expireAt: req.newExpiration,
},
});

// Return the updated token
return {
token: tokenFromRow(newToken),
};
}
51 changes: 48 additions & 3 deletions modules/tokens/tests/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RuntimeError, test, TestContext } from "../_gen/test.ts";
import {
assertEquals,
assertRejects,
assertGreater,
} from "https://deno.land/[email protected]/assert/mod.ts";

test(
Expand All @@ -10,7 +11,7 @@ test(
const error = await assertRejects(async () => {
await ctx.modules.tokens.validate({ token: "invalid token" });
}, RuntimeError);
assertEquals(error.code, "TOKEN_NOT_FOUND");
assertEquals(error.code, "token_not_found");
},
);

Expand All @@ -27,7 +28,7 @@ test(
const error = await assertRejects(async () => {
await ctx.modules.tokens.validate({ token: token.token });
}, RuntimeError);
assertEquals(error.code, "TOKEN_REVOKED");
assertEquals(error.code, "token_revoked");
},
);

Expand All @@ -52,6 +53,50 @@ test(
const error = await assertRejects(async () => {
await ctx.modules.tokens.validate({ token: token.token });
}, RuntimeError);
assertEquals(error.code, "TOKEN_EXPIRED");
assertEquals(error.code, "token_expired");
},
);

test(
"validate token extended not expired",
async (ctx: TestContext) => {
const { token } = await ctx.modules.tokens.create({
type: "test",
meta: { foo: "bar" },
// Set initial expiration to 200ms in the future
expireAt: new Date(Date.now() + 200).toISOString(),
});

// Token should be valid
const validateRes = await ctx.modules.tokens.validate({
token: token.token,
});
assertEquals(token.id, validateRes.token.id);

// Extend token expiration by 10 seconds
await ctx.modules.tokens.extend({
token: token.token,
newExpiration: new Date(Date.now() + 10000).toISOString(),
});

// Wait for 0.5 seconds to ensure token WOULD HAVE expired without
// extension.
await new Promise((resolve) => setTimeout(resolve, 500));

// Token should STILL be valid, and have a different `expireAt` time
const validateResAfterWait = await ctx.modules.tokens.validate({
token: token.token,
});

// Assert that everything except `expireAt` is the same and `expireAt`
// is greater.
assertGreater(validateResAfterWait.token.expireAt, token.expireAt);
assertEquals({
...validateResAfterWait.token,
expireAt: null,
}, {
...token,
expireAt: null,
})
},
);
Loading