From 86c5435b792b7845092c0f1eff79f38278fab72b Mon Sep 17 00:00:00 2001 From: Kyle June Date: Sun, 3 Apr 2022 18:59:53 -0500 Subject: [PATCH] Refactor and remove requireRefresh from getAccessToken (#37) * Update dependencies * Refactor and remove requireRefresh from getAccessToken --- README.md | 14 +- adapters/oak/README.md | 10 +- adapters/oak/context_test.ts | 26 +- adapters/oak/deps.ts | 4 +- adapters/oak/resource_server.ts | 12 +- asserts.ts | 46 +- asserts_test.ts | 12 +- authorization_server.ts | 3 +- authorization_server_test.ts | 409 +++++++++--------- basic_auth_test.ts | 16 +- common_test.ts | 10 +- context.ts | 1 + context_test.ts | 22 +- deps.ts | 10 +- errors.ts | 55 ++- errors_test.ts | 24 +- examples/oak-localstorage/deps.ts | 7 +- examples/oak-localstorage/main.ts | 217 +++++++--- examples/oak-localstorage/models/session.ts | 3 - examples/oak-localstorage/oauth2.ts | 88 +--- examples/oak-localstorage/services/session.ts | 32 -- examples/oak-localstorage/services/token.ts | 31 +- grants/authorization_code_test.ts | 147 ++++--- grants/client_credentials_test.ts | 43 +- grants/grant_test.ts | 119 +++-- grants/password_test.ts | 43 +- grants/refresh_token_test.ts | 87 ++-- models/scope_test.ts | 34 +- pkce_test.ts | 8 +- resource_server.ts | 14 +- resource_server_test.ts | 272 +++--------- services/authorization_code_test.ts | 12 +- services/client_test.ts | 8 +- services/test_services.ts | 4 +- services/token.ts | 15 +- services/token_test.ts | 38 +- services/user_test.ts | 14 +- test_deps.ts | 18 +- 38 files changed, 904 insertions(+), 1024 deletions(-) diff --git a/README.md b/README.md index 86ab87f..11b94de 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # OAuth2 Server -[![version](https://img.shields.io/badge/release-0.11.1-success)](https://deno.land/x/oauth2_server@0.11.1) -[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/oauth2_server@0.11.1/authorization_server.ts) +[![version](https://img.shields.io/badge/release-0.12.0-success)](https://deno.land/x/oauth2_server@0.12.0) +[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/oauth2_server@0.12.0/authorization_server.ts) [![CI](https://github.com/udibo/oauth2_server/workflows/CI/badge.svg)](https://github.com/udibo/oauth2_server/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/udibo/oauth2_server/branch/main/graph/badge.svg?token=8Q7TSUFWUY)](https://codecov.io/gh/udibo/oauth2_server) [![license](https://img.shields.io/github/license/udibo/oauth2_server)](https://github.com/udibo/oauth2_server/blob/master/LICENSE) @@ -44,9 +44,9 @@ also acting as an authorization server. ```ts // Import from Deno's third party module registry -import { ResourceServer } from "https://deno.land/x/oauth2_server@0.11.1/resource_server.ts"; +import { ResourceServer } from "https://deno.land/x/oauth2_server@0.12.0/resource_server.ts"; // Import from GitHub -import { ResourceServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.11.1/resource_server.ts"; +import { ResourceServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.12.0/resource_server.ts"; ``` The AuthorizationServer is an extension of the ResourceServer, adding methods @@ -54,9 +54,9 @@ used by the authorize and token endpoints. ```ts // Import from Deno's third party module registry -import { AuthorizationServer } from "https://deno.land/x/oauth2_server@0.11.1/authorization_server.ts"; +import { AuthorizationServer } from "https://deno.land/x/oauth2_server@0.12.0/authorization_server.ts"; // Import from GitHub -import { AuthorizationServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.11.1/authorization_server.ts"; +import { AuthorizationServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.12.0/authorization_server.ts"; ``` ## Usage @@ -66,7 +66,7 @@ An example of how to use this module can be found but it should give you an idea of how to use this module. See -[deno docs](https://doc.deno.land/https/deno.land/x/oauth2_server@0.11.1/authorization_server.ts) +[deno docs](https://doc.deno.land/https/deno.land/x/oauth2_server@0.12.0/authorization_server.ts) for more information. ### Grants diff --git a/adapters/oak/README.md b/adapters/oak/README.md index 37f787c..7565c99 100644 --- a/adapters/oak/README.md +++ b/adapters/oak/README.md @@ -20,9 +20,9 @@ also acting as an authorization server. ```ts // Import from Deno's third party module registry -import { OakResourceServer } from "https://deno.land/x/oauth2_server@0.11.1/adapters/oak/resource_server.ts"; +import { OakResourceServer } from "https://deno.land/x/oauth2_server@0.12.0/adapters/oak/resource_server.ts"; // Import from GitHub -import { OakResourceServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.11.1/adapters/oak/resource_server.ts"; +import { OakResourceServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.12.0/adapters/oak/resource_server.ts"; ``` The AuthorizationServer is an extension of the ResourceServer, adding methods @@ -30,9 +30,9 @@ used by the authorize and token endpoints. ```ts // Import from Deno's third party module registry -import { OakAuthorizationServer } from "https://deno.land/x/oauth2_server@0.11.1/adapters/oak/authorization_server.ts"; +import { OakAuthorizationServer } from "https://deno.land/x/oauth2_server@0.12.0/adapters/oak/authorization_server.ts"; // Import from GitHub -import { OakAuthorizationServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.11.1/adapters/oak/authorization_server.ts"; +import { OakAuthorizationServer } from "https://raw.githubusercontent.com/udibo/oauth2_server/0.12.0/adapters/oak/authorization_server.ts"; ``` ## Usage @@ -42,5 +42,5 @@ An example of how to use this adapter module can be found but it should give you an idea of how to use this module. See -[deno docs](https://doc.deno.land/https/deno.land/x/oauth2_server@0.11.1/adapters/oak/authorization_server.ts) +[deno docs](https://doc.deno.land/https/deno.land/x/oauth2_server@0.12.0/adapters/oak/authorization_server.ts) for more information. diff --git a/adapters/oak/context_test.ts b/adapters/oak/context_test.ts index ac54a9a..4c0a178 100644 --- a/adapters/oak/context_test.ts +++ b/adapters/oak/context_test.ts @@ -4,17 +4,17 @@ import { assertSpyCall, assertSpyCalls, assertStrictEquals, + describe, + it, Spy, spy, - test, - TestSuite, } from "../../test_deps.ts"; import { BodyForm, Context, Request, Response } from "./deps.ts"; import { OakOAuth2Request, OakOAuth2Response } from "./context.ts"; -const requestTests = new TestSuite({ name: "OakOAuth2Request" }); +const requestTests = describe("OakOAuth2Request"); -test(requestTests, "get", async () => { +it(requestTests, "get", async () => { const expectedBody = new URLSearchParams(); const original: Request = { url: new URL("https://example.com/resource/1"), @@ -35,7 +35,7 @@ test(requestTests, "get", async () => { assertStrictEquals(await wrapped.body, expectedBody); }); -test(requestTests, "post", async () => { +it(requestTests, "post", async () => { const expectedBody: URLSearchParams = new URLSearchParams({ grant_type: "client_credentials", }); @@ -58,7 +58,7 @@ test(requestTests, "post", async () => { assertStrictEquals(await wrapped.body, expectedBody); }); -test(requestTests, "post with sync body error", async () => { +it(requestTests, "post with sync body error", async () => { const original: Request = { url: new URL("https://example.com/token"), method: "POST", @@ -77,7 +77,7 @@ test(requestTests, "post with sync body error", async () => { assertEquals(await wrapped.body, new URLSearchParams()); }); -test(requestTests, "post with async body error", async () => { +it(requestTests, "post with async body error", async () => { const original: Request = { url: new URL("https://example.com/token"), method: "POST", @@ -97,9 +97,9 @@ test(requestTests, "post with async body error", async () => { await assertRejects(() => wrapped.body, Error, "failed"); }); -const responseTests = new TestSuite({ name: "OakOAuth2Response" }); +const responseTests = describe("OakOAuth2Response"); -test(responseTests, "redirect", () => { +it(responseTests, "redirect", () => { const original: Response = { redirect: (_url: string | URL) => undefined, } as Response; @@ -123,7 +123,7 @@ test(responseTests, "redirect", () => { assertSpyCalls(redirect, 2); }); -test(responseTests, "without body", () => { +it(responseTests, "without body", () => { const headers: Headers = new Headers({ "Content-Type": `application/json` }); const original: Response = { status: 404, @@ -149,7 +149,7 @@ test(responseTests, "without body", () => { assertStrictEquals(original.body, undefined); }); -test(responseTests, "with sync body value", () => { +it(responseTests, "with sync body value", () => { const headers: Headers = new Headers({ "Content-Type": `application/json` }); const original: Response = { status: 200, @@ -168,7 +168,7 @@ test(responseTests, "with sync body value", () => { assertStrictEquals(original.body, body); }); -test(responseTests, "with async body value", () => { +it(responseTests, "with async body value", () => { const headers: Headers = new Headers({ "Content-Type": `application/json` }); const original: Response = { status: 200, @@ -189,7 +189,7 @@ test(responseTests, "with async body value", () => { assertStrictEquals(result, body); }); -test(responseTests, "with body function", () => { +it(responseTests, "with body function", () => { const headers: Headers = new Headers({ "Content-Type": `application/json` }); const original: Response = { status: 200, diff --git a/adapters/oak/deps.ts b/adapters/oak/deps.ts index a6d8851..ff7498f 100644 --- a/adapters/oak/deps.ts +++ b/adapters/oak/deps.ts @@ -3,8 +3,8 @@ export { Cookies, Request, Response, -} from "https://deno.land/x/oak@v10.1.0/mod.ts"; +} from "https://deno.land/x/oak@v10.5.1/mod.ts"; export type { BodyForm, Middleware, -} from "https://deno.land/x/oak@v10.1.0/mod.ts"; +} from "https://deno.land/x/oak@v10.5.1/mod.ts"; diff --git a/adapters/oak/resource_server.ts b/adapters/oak/resource_server.ts index 755b706..ff239d6 100644 --- a/adapters/oak/resource_server.ts +++ b/adapters/oak/resource_server.ts @@ -37,7 +37,6 @@ export class OakResourceServer< private stateKey: string; private getAccessToken: ( request: OakOAuth2Request, - requireRefresh?: boolean, ) => Promise; constructor(options: OakResourceServerOptions) { @@ -59,16 +58,19 @@ export class OakResourceServer< return state; } + async getToken(accessToken: string): Promise> { + return await this.server.getToken(accessToken); + } + async getTokenForRequest( request: OakOAuth2Request, - ): Promise | undefined> { - return await this.server.getTokenForRequest(request, this.getAccessToken) - .catch(() => undefined); + ): Promise> { + return await this.server.getTokenForRequest(request, this.getAccessToken); } async getTokenForContext( context: Context, - ): Promise | undefined> { + ): Promise> { const state = this.getState(context); const { request } = state; return await this.getTokenForRequest(request); diff --git a/asserts.ts b/asserts.ts index ba7a888..b0c0be3 100644 --- a/asserts.ts +++ b/asserts.ts @@ -4,8 +4,6 @@ import { assertSpyCall, assertStrictEquals, Spy, - SpyCall, - Stub, } from "./test_deps.ts"; import { ClientInterface } from "./models/client.ts"; import { ScopeInterface } from "./models/scope.ts"; @@ -13,18 +11,18 @@ import { Token } from "./models/token.ts"; import { AuthorizationCode } from "./models/authorization_code.ts"; export function assertScope( - actual: Scope | null | undefined, + actual: unknown, expected: Scope | null | undefined, ): void { try { if (expected && actual) { - assert(expected.equals(actual)); + assert(expected.equals(actual as Scope)); } else { assertEquals(actual, expected); } } catch { assertEquals( - actual ? [...actual].sort() : actual, + actual ? [...actual as Scope].sort() : actual, expected ? [...expected].sort() : expected, ); } @@ -35,14 +33,17 @@ function assertWithoutScope< User, Scope extends ScopeInterface, >( - actual: - | Partial> - | Partial>, + actual: unknown, expected: | Partial> | Partial>, ): void { - const actualWithoutScope = { ...actual }; + const actualWithoutScope = { + ...(actual as ( + | Partial> + | Partial> + )), + }; delete actualWithoutScope.scope; const expectedWithoutScope = { ...expected }; delete expectedWithoutScope.scope; @@ -54,7 +55,7 @@ export function assertToken< User, Scope extends ScopeInterface, >( - actual: Partial> | null | undefined, + actual: unknown, expected: Partial> | null | undefined, ): void { assert( @@ -62,7 +63,10 @@ export function assertToken< actual ? "did not expect token" : "expected token", ); if (actual && expected) { - assertScope(actual.scope, expected.scope); + assertScope( + (actual as Partial>).scope, + expected.scope, + ); assertWithoutScope(actual, expected); } } @@ -72,7 +76,7 @@ export function assertAuthorizationCode< User, Scope extends ScopeInterface, >( - actual: Partial> | null | undefined, + actual: unknown, expected: Partial> | null | undefined, ): void { assert( @@ -82,7 +86,10 @@ export function assertAuthorizationCode< : "expected authorization code", ); if (actual && expected) { - assertScope(actual.scope, expected.scope); + assertScope( + (actual as Partial>).scope, + expected.scope, + ); assertWithoutScope(actual, expected); } } @@ -92,18 +99,17 @@ export function assertClientUserScopeCall< User, Scope extends ScopeInterface, >( - // deno-lint-ignore no-explicit-any - spy: Spy | Stub, + spy: Spy, callIndex: number, - // deno-lint-ignore no-explicit-any - self: any, + self: unknown, client: Client, user: User, - expectedScope?: Scope | null, + scope?: Scope | null, ): void { - const call: SpyCall = assertSpyCall(spy, callIndex); + assertSpyCall(spy, callIndex); + const call = spy.calls[callIndex]; assertStrictEquals(call.self, self); assertEquals(call.args.slice(0, 2), [client, user]); const actualScope: ScopeInterface | undefined = call.args[2]; - assertScope(actualScope, expectedScope); + assertScope(actualScope, scope); } diff --git a/asserts_test.ts b/asserts_test.ts index 15850b2..edbd940 100644 --- a/asserts_test.ts +++ b/asserts_test.ts @@ -3,18 +3,16 @@ import { assertScope, assertToken, } from "./asserts.ts"; -import { AssertionError, assertThrows, test, TestSuite } from "./test_deps.ts"; +import { AssertionError, assertThrows, describe, it } from "./test_deps.ts"; import { Client } from "./models/client.ts"; import { Scope } from "./models/scope.ts"; import { Token } from "./models/token.ts"; import { AuthorizationCode } from "./models/authorization_code.ts"; import { User } from "./models/user.ts"; -const assertsTests: TestSuite = new TestSuite({ - name: "asserts", -}); +const assertsTests = describe("asserts"); -test(assertsTests, "assertScope", () => { +it(assertsTests, "assertScope", () => { assertScope(undefined, undefined); assertScope(new Scope(), new Scope()); assertScope(new Scope("read"), new Scope("read")); @@ -56,7 +54,7 @@ test(assertsTests, "assertScope", () => { const client: Client = { id: "1", grants: [] }; const user: User = { username: "kyle" }; -test(assertsTests, "assertToken", () => { +it(assertsTests, "assertToken", () => { const expectedToken: Token = { accessToken: "x", client: { id: "1", grants: [] }, @@ -169,7 +167,7 @@ test(assertsTests, "assertToken", () => { ); }); -test(assertsTests, "assertAuthorizationCode", () => { +it(assertsTests, "assertAuthorizationCode", () => { const expiresAt = new Date(Date.now() + 60000); const expectedAuthorizationCode: AuthorizationCode = { code: "x", diff --git a/authorization_server.ts b/authorization_server.ts index c764584..db42324 100644 --- a/authorization_server.ts +++ b/authorization_server.ts @@ -366,6 +366,7 @@ export { UNICODECHARNOCRLF, UnsupportedGrantTypeError, UnsupportedResponseTypeError, + UnsupportedTokenTypeError, VSCHAR, } from "./resource_server.ts"; export type { @@ -381,7 +382,7 @@ export type { OAuth2AuthenticatedRequest, OAuth2AuthorizedRequest, OAuth2AuthorizeRequest, - OAuth2ErrorInit, + OAuth2ErrorOptions, OAuth2Request, OAuth2Response, RefreshToken, diff --git a/authorization_server_test.ts b/authorization_server_test.ts index 481fde9..385005d 100644 --- a/authorization_server_test.ts +++ b/authorization_server_test.ts @@ -4,12 +4,12 @@ import { assertSpyCall, assertSpyCalls, delay, + describe, + it, Spy, spy, Stub, stub, - test, - TestSuite, } from "./test_deps.ts"; import { fakeAuthorizeRequest, @@ -56,7 +56,7 @@ import { User, } from "./authorization_server.ts"; -test("verify exports", () => { +it("verify exports", () => { const moduleKeys = Object.keys(authorizationServerModule).sort(); const moduleKeySet = new Set(moduleKeys); const missingKeys = []; @@ -97,6 +97,7 @@ test("verify exports", () => { "UnauthorizedClientError", "UnsupportedGrantTypeError", "UnsupportedResponseTypeError", + "UnsupportedTokenTypeError", "VSCHAR", "authorizeParameters", "authorizeUrl", @@ -132,37 +133,35 @@ const server = new AuthorizationServer({ services: { tokenService }, }); -const serverTests = new TestSuite({ - name: "AuthorizationServer", -}); +const serverTests = describe("AuthorizationServer"); interface TokenTestContext { - success: Spy; - error: Spy; - tokenSuccess: Stub>; - tokenError: Stub>; + success: Spy; + error: Spy; + tokenSuccess: Stub; + tokenError: Stub; } -const tokenTests = new TestSuite({ +const tokenTests = describe({ name: "token", suite: serverTests, - beforeEach(context: TokenTestContext) { - context.success = spy(); - context.error = spy(); - context.tokenSuccess = stub( + beforeEach() { + this.success = spy(); + this.error = spy(); + this.tokenSuccess = stub( server, "tokenSuccess", - () => delay(0).then(context.success), + () => delay(0).then(this.success), ); - context.tokenError = stub( + this.tokenError = stub( server, "tokenError", - () => delay(0).then(context.error), + () => delay(0).then(this.error), ); }, - afterEach({ tokenSuccess, tokenError }) { - tokenSuccess.restore(); - tokenError.restore(); + afterEach() { + this.tokenSuccess.restore(); + this.tokenError.restore(); }, }); @@ -181,7 +180,8 @@ async function tokenTestError( assertSpyCalls(tokenSuccess, 0); assertSpyCalls(success, 0); - const call = assertSpyCall(tokenError, 0, { self: server }); + assertSpyCall(tokenError, 0, { self: server }); + const call = tokenError.calls[0]; assertEquals(call.args.length, 3); assertEquals(call.args.slice(0, 2), [request, response]); assertIsError(call.args[2], ErrorClass, msgIncludes, msg); @@ -194,15 +194,15 @@ async function tokenTestError( assertSpyCalls(redirect, 0); } -test( +it( tokenTests, "method must be post", - async (context) => { + async function () { const request = fakeTokenRequest(); request.method = "GET"; const response = fakeResponse(); await tokenTestError( - context, + this, request, response, InvalidRequestError, @@ -211,15 +211,15 @@ test( }, ); -test( +it( tokenTests, "content-type header required", - async (context) => { + async function () { const request = fakeTokenRequest(); const response = fakeResponse(); request.headers.delete("Content-Type"); await tokenTestError( - context, + this, request, response, InvalidRequestError, @@ -228,16 +228,16 @@ test( }, ); -test( +it( tokenTests, "content-type header must be application/x-www-form-urlencoded", - async (context) => { + async function () { const request = fakeTokenRequest(); request.headers.set("Content-Type", "application/json"); const response = fakeResponse(); request.headers.delete("Content-Type"); await tokenTestError( - context, + this, request, response, InvalidRequestError, @@ -246,11 +246,11 @@ test( }, ); -test(tokenTests, "grant_type parameter required", async (context) => { +it(tokenTests, "grant_type parameter required", async function () { const request = fakeTokenRequest(""); const response = fakeResponse(); await tokenTestError( - context, + this, request, response, InvalidRequestError, @@ -258,11 +258,11 @@ test(tokenTests, "grant_type parameter required", async (context) => { ); }); -test(tokenTests, "invalid grant_type", async (context) => { +it(tokenTests, "invalid grant_type", async function () { const request = fakeTokenRequest("grant_type=fake"); const response = fakeResponse(); await tokenTestError( - context, + this, request, response, UnsupportedGrantTypeError, @@ -270,7 +270,7 @@ test(tokenTests, "invalid grant_type", async (context) => { ); }); -test(tokenTests, "client authentication failed", async (context) => { +it(tokenTests, "client authentication failed", async function () { const getAuthenticatedClient = stub( refreshTokenGrant, "getAuthenticatedClient", @@ -281,7 +281,7 @@ test(tokenTests, "client authentication failed", async (context) => { const request = fakeTokenRequest("grant_type=refresh_token"); const response = fakeResponse(); await tokenTestError( - context, + this, request, response, InvalidClientError, @@ -297,10 +297,10 @@ test(tokenTests, "client authentication failed", async (context) => { } }); -test( +it( tokenTests, "client is not authorized to use this grant_type", - async (context) => { + async function () { const getAuthenticatedClient = stub( refreshTokenGrant, "getAuthenticatedClient", @@ -316,7 +316,7 @@ test( ); const response = fakeResponse(); await tokenTestError( - context, + this, request, response, UnauthorizedClientError, @@ -333,7 +333,7 @@ test( }, ); -test(tokenTests, "grant token error", async (context) => { +it(tokenTests, "grant token error", async function () { const token = stub( refreshTokenGrant, "token", @@ -343,7 +343,7 @@ test(tokenTests, "grant token error", async (context) => { const request = fakeTokenRequest("grant_type=refresh_token"); const response = fakeResponse(); await tokenTestError( - context, + this, request, response, InvalidGrantError, @@ -359,10 +359,11 @@ test(tokenTests, "grant token error", async (context) => { } }); -test( +it( tokenTests, "returns refresh token", - async ({ success, error, tokenSuccess, tokenError }) => { + async function () { + const { success, error, tokenSuccess, tokenError } = this; const refreshToken: RefreshToken = { accessToken: "foo", refreshToken: "bar", @@ -401,12 +402,12 @@ test( }, ); -const bearerTokenTests = new TestSuite({ +const bearerTokenTests = describe({ name: "bearerToken", suite: serverTests, }); -test(bearerTokenTests, "without optional token properties", () => { +it(bearerTokenTests, "without optional token properties", () => { const accessToken: AccessToken = { accessToken: "foo", client, @@ -419,7 +420,7 @@ test(bearerTokenTests, "without optional token properties", () => { }); }); -test(bearerTokenTests, "with optional token properties", () => { +it(bearerTokenTests, "with optional token properties", () => { const refreshToken: RefreshToken = { accessToken: "foo", refreshToken: "bar", @@ -436,7 +437,7 @@ test(bearerTokenTests, "with optional token properties", () => { }); }); -test(serverTests, "tokenResponse", async () => { +it(serverTests, "tokenResponse", async () => { const request = fakeTokenRequest(); const response = fakeResponse(); const redirect = spy(response, "redirect"); @@ -451,7 +452,7 @@ test(serverTests, "tokenResponse", async () => { assertSpyCalls(redirect, 0); }); -test(serverTests, "tokenSuccess", async () => { +it(serverTests, "tokenSuccess", async () => { const tokenResponseAwait = spy(); const tokenResponse = stub( server, @@ -480,9 +481,10 @@ test(serverTests, "tokenSuccess", async () => { assertSpyCalls(tokenResponse, 1); assertSpyCalls(tokenResponseAwait, 1); - const call = assertSpyCall(bearerToken, 0, { + assertSpyCall(bearerToken, 0, { args: [request.token], }); + const call = bearerToken.calls[0]; assertSpyCalls(bearerToken, 1); assertEquals(response.status, 200); @@ -493,7 +495,7 @@ test(serverTests, "tokenSuccess", async () => { } }); -test(serverTests, "tokenError handles error", async () => { +it(serverTests, "tokenError handles error", async () => { const tokenResponseAwait = spy(); const tokenResponse = stub( server, @@ -547,34 +549,34 @@ interface AuthorizeTestContext { consent: Spy; } -const authorizeTests = new TestSuite({ +const authorizeTests = describe({ name: "authorize", suite: serverTests, - beforeEach(context: AuthorizeTestContext) { - context.success = spy(); - context.error = spy(); - context.authorizeSuccess = stub( + beforeEach() { + this.success = spy(); + this.error = spy(); + this.authorizeSuccess = stub( server, "authorizeSuccess", - () => delay(0).then(context.success), + () => delay(0).then(this.success), ); - context.authorizeError = stub( + this.authorizeError = stub( server, "authorizeError", - () => delay(0).then(context.error), + () => delay(0).then(this.error), ); - context.setAuthorizationAwait = spy(); - context.setAuthorization = spy(() => - delay(0).then(context.setAuthorizationAwait) + this.setAuthorizationAwait = spy(); + this.setAuthorization = spy(() => + delay(0).then(this.setAuthorizationAwait) ); - context.login = spy(); - context.consent = spy(); + this.login = spy(); + this.consent = spy(); }, - afterEach({ authorizeSuccess, authorizeError }) { - authorizeSuccess.restore(); - authorizeError.restore(); + afterEach() { + this.authorizeSuccess.restore(); + this.authorizeError.restore(); }, }); @@ -602,7 +604,8 @@ async function authorizeTestError( assertSpyCalls(authorizeSuccess, 0); assertSpyCalls(success, 0); - const call = assertSpyCall(authorizeError, 0, { self: server }); + assertSpyCall(authorizeError, 0, { self: server }); + const call = authorizeError.calls[0]; assertEquals(call.args.length, 5); assertEquals(call.args.slice(0, 2), [request, response]); assertIsError(call.args[2], ErrorClass, msgIncludes, msg); @@ -645,17 +648,17 @@ async function authorizeTestErrorNoRedirect( assertSpyCalls(setAuthorization, 0); } -test( +it( authorizeTests, "missing authorization code grant", - async (context) => { + async function () { const { grants } = server; try { server.grants = { "refresh_token": refreshTokenGrant }; const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorNoRedirect( - context, + this, request, response, ServerError, @@ -667,15 +670,15 @@ test( }, ); -test( +it( authorizeTests, "client_id parameter required", - async (context) => { + async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.delete("client_id"); const response = fakeResponse(); await authorizeTestErrorNoRedirect( - context, + this, request, response, InvalidRequestError, @@ -684,10 +687,10 @@ test( }, ); -test( +it( authorizeTests, "client not found", - async (context) => { + async function () { const clientServiceGet: Stub = stub( clientService, "get", @@ -697,7 +700,7 @@ test( const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorNoRedirect( - context, + this, request, response, InvalidClientError, @@ -709,10 +712,10 @@ test( }, ); -test( +it( authorizeTests, "client is not authorized to use the authorization code grant type", - async (context) => { + async function () { const clientServiceGet: Stub = stub( clientService, "get", @@ -722,7 +725,7 @@ test( const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorNoRedirect( - context, + this, request, response, UnauthorizedClientError, @@ -734,7 +737,7 @@ test( }, ); -test(authorizeTests, "no authorized redirect_uri", async (context) => { +it(authorizeTests, "no authorized redirect_uri", async function () { const clientServiceGet: Stub = stub( clientService, "get", @@ -744,7 +747,7 @@ test(authorizeTests, "no authorized redirect_uri", async (context) => { const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorNoRedirect( - context, + this, request, response, UnauthorizedClientError, @@ -755,12 +758,12 @@ test(authorizeTests, "no authorized redirect_uri", async (context) => { } }); -test(authorizeTests, "redirect_uri not authorized", async (context) => { +it(authorizeTests, "redirect_uri not authorized", async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.set("redirect_uri", "http://client.example.com/cb"); const response = fakeResponse(); await authorizeTestErrorNoRedirect( - context, + this, request, response, UnauthorizedClientError, @@ -818,12 +821,12 @@ async function authorizeTestErrorPreAuthorization( assertSpyCalls(setAuthorization, 0); } -test(authorizeTests, "state required", async (context) => { +it(authorizeTests, "state required", async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.delete("state"); const response = fakeResponse(); await authorizeTestErrorPreAuthorization( - context, + this, request, response, InvalidRequestError, @@ -831,12 +834,12 @@ test(authorizeTests, "state required", async (context) => { ); }); -test(authorizeTests, "response_type required", async (context) => { +it(authorizeTests, "response_type required", async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.delete("response_type"); const response = fakeResponse(); await authorizeTestErrorPreAuthorization( - context, + this, request, response, InvalidRequestError, @@ -844,12 +847,12 @@ test(authorizeTests, "response_type required", async (context) => { ); }); -test(authorizeTests, "response_type not supported", async (context) => { +it(authorizeTests, "response_type not supported", async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.set("response_type", "token"); const response = fakeResponse(); await authorizeTestErrorPreAuthorization( - context, + this, request, response, InvalidRequestError, @@ -857,15 +860,15 @@ test(authorizeTests, "response_type not supported", async (context) => { ); }); -test( +it( authorizeTests, "code_challenge required when code_challenge_method is set", - async (context) => { + async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.set("code_challenge_method", "S256"); const response = fakeResponse(); await authorizeTestErrorPreAuthorization( - context, + this, request, response, InvalidRequestError, @@ -874,12 +877,12 @@ test( }, ); -test(authorizeTests, "code_challenge_method required", async (context) => { +it(authorizeTests, "code_challenge_method required", async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.set("code_challenge", "abc"); const response = fakeResponse(); await authorizeTestErrorPreAuthorization( - context, + this, request, response, InvalidRequestError, @@ -887,13 +890,13 @@ test(authorizeTests, "code_challenge_method required", async (context) => { ); }); -test(authorizeTests, "unsupported code_challenge_method", async (context) => { +it(authorizeTests, "unsupported code_challenge_method", async function () { const request = fakeAuthorizeRequest(); request.url.searchParams.set("code_challenge", "abc"); request.url.searchParams.set("code_challenge_method", "plain"); const response = fakeResponse(); await authorizeTestErrorPreAuthorization( - context, + this, request, response, InvalidRequestError, @@ -936,7 +939,7 @@ async function authorizeTestErrorAuthorized( assertSpyCalls(setAuthorizationAwait, 1); } -test(authorizeTests, "authentication required with PKCE", async (context) => { +it(authorizeTests, "authentication required with PKCE", async function () { const request = fakeAuthorizeRequest(); const verifier: string = generateCodeVerifier(); const challenge: string = await challengeMethods.S256(verifier); @@ -944,7 +947,7 @@ test(authorizeTests, "authentication required with PKCE", async (context) => { request.url.searchParams.set("code_challenge_method", "S256"); const response = fakeResponse(); await authorizeTestErrorAuthorized( - context, + this, request, response, undefined, @@ -954,14 +957,14 @@ test(authorizeTests, "authentication required with PKCE", async (context) => { ); }); -test( +it( authorizeTests, "authentication required without PKCE", - async (context) => { + async function () { const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorAuthorized( - context, + this, request, response, undefined, @@ -972,10 +975,10 @@ test( }, ); -test( +it( authorizeTests, "scope not accepted", - async (context) => { + async function () { const acceptedScope = stub( authorizationCodeGrant, "acceptedScope", @@ -985,7 +988,7 @@ test( const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorAuthorized( - context, + this, request, response, user, @@ -999,14 +1002,14 @@ test( }, ); -test( +it( authorizeTests, "not authorized", - async (context) => { + async function () { const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorAuthorized( - context, + this, request, response, user, @@ -1017,14 +1020,14 @@ test( }, ); -test( +it( authorizeTests, "not fully authorized", - async (context) => { + async function () { const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorAuthorized( - context, + this, request, response, user, @@ -1035,7 +1038,7 @@ test( }, ); -test(authorizeTests, "generateAuthorizationCode error", async (context) => { +it(authorizeTests, "generateAuthorizationCode error", async function () { const generateAuthorizationCode = stub( authorizationCodeGrant, "generateAuthorizationCode", @@ -1045,7 +1048,7 @@ test(authorizeTests, "generateAuthorizationCode error", async (context) => { const request = fakeAuthorizeRequest(); const response = fakeResponse(); await authorizeTestErrorAuthorized( - context, + this, request, response, user, @@ -1058,7 +1061,7 @@ test(authorizeTests, "generateAuthorizationCode error", async (context) => { } }); -async function authorizeTest( +async function authorizeit( context: AuthorizeTestContext, request: OAuth2Request, response: OAuth2Response, @@ -1174,11 +1177,11 @@ async function authorizeTest( assertSpyCalls(setAuthorizationAwait, 1); } -test(authorizeTests, "success without PKCE", async (context) => { +it(authorizeTests, "success without PKCE", async function () { const request = fakeAuthorizeRequest(); const response = fakeResponse(); - await authorizeTest( - context, + await authorizeit( + this, request, response, "123", @@ -1187,15 +1190,15 @@ test(authorizeTests, "success without PKCE", async (context) => { ); }); -test(authorizeTests, "success with PKCE", async (context) => { +it(authorizeTests, "success with PKCE", async function () { const request = fakeAuthorizeRequest(); const verifier: string = generateCodeVerifier(); const challenge: string = await challengeMethods.S256(verifier); request.url.searchParams.set("code_challenge", challenge); request.url.searchParams.set("code_challenge_method", "S256"); const response = fakeResponse(); - await authorizeTest( - context, + await authorizeit( + this, request, response, "123", @@ -1206,7 +1209,7 @@ test(authorizeTests, "success with PKCE", async (context) => { ); }); -test(serverTests, "authorizeSuccess", async () => { +it(serverTests, "authorizeSuccess", async () => { const request = fakeAuthorizeRequest(); const expectedRedirectUrl = new URL( "https://client.example.com/cb?state=xyz&code=123", @@ -1247,83 +1250,89 @@ interface AuthorizeTestContext { errorHandlerAwait: Spy; } -const authorizeErrorTests = new TestSuite({ +const authorizeErrorTests = describe({ name: "authorizeError", suite: serverTests, - async beforeEach(context: AuthorizeTestContext) { - context.request = fakeAuthorizeRequest(); - context.request.authorizeParameters = await authorizeParameters( - context.request, + async beforeEach() { + this.request = fakeAuthorizeRequest(); + this.request.authorizeParameters = await authorizeParameters( + this.request, ); - context.request.redirectUrl = new URL( + this.request.redirectUrl = new URL( "https://client.example.com/cb?state=xyz", ); - context.response = fakeResponse(); - context.redirectAwait = spy(); - context.redirect = stub( - context.response, + this.response = fakeResponse(); + this.redirectAwait = spy(); + this.redirect = stub( + this.response, "redirect", - () => delay(0).then(context.redirectAwait), + () => delay(0).then(this.redirectAwait), ); - context.loginAwait = spy(); - context.login = spy(() => delay(0).then(context.loginAwait)); - context.consentAwait = spy(); - context.consent = spy(() => delay(0).then(context.consentAwait)); - context.errorHandlerAwait = spy(); - context.errorHandler = stub( + this.loginAwait = spy(); + this.login = spy(() => delay(0).then(this.loginAwait)); + this.consentAwait = spy(); + this.consent = spy(() => delay(0).then(this.consentAwait)); + this.errorHandlerAwait = spy(); + this.errorHandler = stub( server, "errorHandler", - () => delay(0).then(context.errorHandlerAwait), + () => delay(0).then(this.errorHandlerAwait), ); }, - afterEach({ errorHandler }) { - errorHandler.restore(); + afterEach() { + this.errorHandler.restore(); }, }); -test(authorizeErrorTests, "non access_denied error with redirectUrl", async ({ - request, - response, - redirect, - redirectAwait, - login, - consent, - errorHandler, -}) => { - const expectedRedirectUrl = new URL(request.redirectUrl!.toString()); - expectedRedirectUrl.searchParams.set("error", "invalid_request"); - expectedRedirectUrl.searchParams.set("error_description", "not valid"); - - const error = new InvalidRequestError("not valid"); - await server.authorizeError(request, response, error, login, consent); +it( + authorizeErrorTests, + "non access_denied error with redirectUrl", + async function () { + const { + request, + response, + redirect, + redirectAwait, + login, + consent, + errorHandler, + } = this; + const expectedRedirectUrl = new URL(request.redirectUrl!.toString()); + expectedRedirectUrl.searchParams.set("error", "invalid_request"); + expectedRedirectUrl.searchParams.set("error_description", "not valid"); - assertEquals([...response.headers.entries()], []); - assertEquals(response.status, undefined); - assertEquals(response.body, undefined); - assertSpyCall(redirect, 0, { - self: response, - args: [expectedRedirectUrl], - }); - assertSpyCalls(redirect, 1); - assertSpyCalls(redirectAwait, 1); + const error = new InvalidRequestError("not valid"); + await server.authorizeError(request, response, error, login, consent); - assertSpyCalls(login, 0); - assertSpyCalls(consent, 0); - assertSpyCalls(errorHandler, 0); -}); + assertEquals([...response.headers.entries()], []); + assertEquals(response.status, undefined); + assertEquals(response.body, undefined); + assertSpyCall(redirect, 0, { + self: response, + args: [expectedRedirectUrl], + }); + assertSpyCalls(redirect, 1); + assertSpyCalls(redirectAwait, 1); -test( + assertSpyCalls(login, 0); + assertSpyCalls(consent, 0); + assertSpyCalls(errorHandler, 0); + }, +); + +it( authorizeErrorTests, "non access_denied error without redirectUrl", - async ({ - request, - response, - redirect, - login, - consent, - errorHandler, - errorHandlerAwait, - }) => { + async function () { + const { + request, + response, + redirect, + login, + consent, + errorHandler, + errorHandlerAwait, + } = this; delete request.redirectUrl; const error = new InvalidRequestError("not valid"); await server.authorizeError(request, response, error, login, consent); @@ -1345,18 +1354,19 @@ test( }, ); -test( +it( authorizeErrorTests, "calls login for access_denied error without user", - async ({ - request, - response, - redirect, - login, - loginAwait, - consent, - errorHandler, - }) => { + async function () { + const { + request, + response, + redirect, + login, + loginAwait, + consent, + errorHandler, + } = this; const error = new AccessDeniedError("authentication required"); await server.authorizeError(request, response, error, login, consent); @@ -1377,18 +1387,19 @@ test( }, ); -test( +it( authorizeErrorTests, "calls consent for access_denied error without consent for requested scope", - async ({ - request, - response, - redirect, - login, - consent, - consentAwait, - errorHandler, - }) => { + async function () { + const { + request, + response, + redirect, + login, + consent, + consentAwait, + errorHandler, + } = this; request.user = user; const error = new AccessDeniedError("not authorized"); await server.authorizeError(request, response, error, login, consent); diff --git a/basic_auth_test.ts b/basic_auth_test.ts index 8fb9001..6acbf1a 100644 --- a/basic_auth_test.ts +++ b/basic_auth_test.ts @@ -1,12 +1,10 @@ import { BasicAuth, parseBasicAuth } from "./basic_auth.ts"; -import { assertEquals, assertThrows, test, TestSuite } from "./test_deps.ts"; +import { assertEquals, assertThrows, describe, it } from "./test_deps.ts"; import { InvalidClientError } from "./errors.ts"; -const parseBasicAuthTests: TestSuite = new TestSuite({ - name: "parseBasicAuth", -}); +const parseBasicAuthTests = describe("parseBasicAuth"); -test(parseBasicAuthTests, "authorization header required", () => { +it(parseBasicAuthTests, "authorization header required", () => { assertThrows( () => parseBasicAuth(null), InvalidClientError, @@ -19,7 +17,7 @@ test(parseBasicAuthTests, "authorization header required", () => { ); }); -test(parseBasicAuthTests, "unsupported authorization header", () => { +it(parseBasicAuthTests, "unsupported authorization header", () => { assertThrows( () => parseBasicAuth("x"), InvalidClientError, @@ -37,7 +35,7 @@ test(parseBasicAuthTests, "unsupported authorization header", () => { ); }); -test( +it( parseBasicAuthTests, "authorization header is not correctly encoded", () => { @@ -54,7 +52,7 @@ test( }, ); -test(parseBasicAuthTests, "authorization header is malformed", () => { +it(parseBasicAuthTests, "authorization header is malformed", () => { assertThrows( () => parseBasicAuth(`basic ${btoa("kyle")}`), InvalidClientError, @@ -72,7 +70,7 @@ test(parseBasicAuthTests, "authorization header is malformed", () => { ); }); -test(parseBasicAuthTests, "returns correct name and pass", () => { +it(parseBasicAuthTests, "returns correct name and pass", () => { let basicAuth: BasicAuth = parseBasicAuth(`basic ${btoa("kyle:")}`); assertEquals(basicAuth, { name: "kyle", pass: "" }); basicAuth = parseBasicAuth(`BASIC ${btoa("kyle:hunter2")}`); diff --git a/common_test.ts b/common_test.ts index 4f6f5d7..0b32037 100644 --- a/common_test.ts +++ b/common_test.ts @@ -1,18 +1,16 @@ import { camelCase, snakeCase } from "./common.ts"; -import { assertStrictEquals, test, TestSuite } from "./test_deps.ts"; +import { assertStrictEquals, describe, it } from "./test_deps.ts"; -const commonTests: TestSuite = new TestSuite({ - name: "common", -}); +const commonTests = describe("common"); -test(commonTests, "camelCase", () => { +it(commonTests, "camelCase", () => { assertStrictEquals(camelCase("a_b_c"), "aBC"); assertStrictEquals(camelCase("A-B-C"), "aBC"); assertStrictEquals(camelCase("two_words"), "twoWords"); assertStrictEquals(camelCase("TWO-WORDS"), "twoWords"); }); -test(commonTests, "snakeCase", () => { +it(commonTests, "snakeCase", () => { assertStrictEquals(snakeCase("aBC"), "a_b_c"); assertStrictEquals(snakeCase("ABC"), "a_b_c"); assertStrictEquals(snakeCase("twoWords"), "two_words"); diff --git a/context.ts b/context.ts index 89bbdd5..a3d247b 100644 --- a/context.ts +++ b/context.ts @@ -175,6 +175,7 @@ export interface LoginRedirectOptions { loginRedirectKey?: string; } +/** Used for redirecting to login page for the authorization code flow. */ export function loginRedirectFactory< Client extends ClientInterface, User, diff --git a/context_test.ts b/context_test.ts index d771579..7aea6f3 100644 --- a/context_test.ts +++ b/context_test.ts @@ -8,13 +8,11 @@ import { Scope } from "./models/scope.ts"; import { User } from "./models/user.ts"; import { challengeMethods, generateCodeVerifier } from "./pkce.ts"; import { fakeAuthorizeRequest } from "./test_context.ts"; -import { assertEquals, test, TestSuite } from "./test_deps.ts"; +import { assertEquals, describe, it } from "./test_deps.ts"; -const authorizeParametersTests = new TestSuite({ - name: "authorizeParameters", -}); +const authorizeParametersTests = describe("authorizeParameters"); -test(authorizeParametersTests, "from search parameters", async () => { +it(authorizeParametersTests, "from search parameters", async () => { const verifier: string = generateCodeVerifier(); const challenge: string = await challengeMethods.S256(verifier); const request = fakeAuthorizeRequest(); @@ -31,7 +29,7 @@ test(authorizeParametersTests, "from search parameters", async () => { }); }); -test(authorizeParametersTests, "from body", async () => { +it(authorizeParametersTests, "from body", async () => { const verifier: string = generateCodeVerifier(); const challenge: string = await challengeMethods.S256(verifier); const request = fakeAuthorizeRequest({ @@ -55,7 +53,7 @@ test(authorizeParametersTests, "from body", async () => { }); }); -test(authorizeParametersTests, "from search parameters and body", async () => { +it(authorizeParametersTests, "from search parameters and body", async () => { const verifier: string = generateCodeVerifier(); const challenge: string = await challengeMethods.S256(verifier); const request = fakeAuthorizeRequest({ @@ -79,7 +77,7 @@ test(authorizeParametersTests, "from search parameters and body", async () => { }); }); -test( +it( authorizeParametersTests, "prefer body over search parameters", async () => { @@ -121,11 +119,9 @@ test( }, ); -const authorizeUrlTests = new TestSuite({ - name: "authorizeUrl", -}); +const authorizeUrlTests = describe("authorizeUrl"); -test(authorizeUrlTests, "without PKCE", async () => { +it(authorizeUrlTests, "without PKCE", async () => { const request = fakeAuthorizeRequest(); const expectedUrl = new URL("https://example.com/authorize"); expectedUrl.searchParams.set("response_type", "code"); @@ -141,7 +137,7 @@ test(authorizeUrlTests, "without PKCE", async () => { ); }); -test(authorizeUrlTests, "with PKCE", async () => { +it(authorizeUrlTests, "with PKCE", async () => { const verifier: string = generateCodeVerifier(); const challenge: string = await challengeMethods.S256(verifier); const request = fakeAuthorizeRequest(); diff --git a/deps.ts b/deps.ts index 4ab73ec..37079e4 100644 --- a/deps.ts +++ b/deps.ts @@ -1,14 +1,14 @@ export { decode as decodeBase64url, encode as encodeBase64url, -} from "https://deno.land/std@0.120.0/encoding/base64url.ts"; +} from "https://deno.land/std@0.133.0/encoding/base64url.ts"; export { encode as encodeHex, -} from "https://deno.land/std@0.120.0/encoding/hex.ts"; -export { resolve } from "https://deno.land/std@0.120.0/path/mod.ts"; +} from "https://deno.land/std@0.133.0/encoding/hex.ts"; +export { resolve } from "https://deno.land/std@0.133.0/path/mod.ts"; export { HttpError, isHttpError, optionsFromArgs, -} from "https://deno.land/x/http_error@0.1.2/mod.ts"; -export type { HttpErrorInit } from "https://deno.land/x/http_error@0.1.2/mod.ts"; +} from "https://deno.land/x/http_error@0.1.3/mod.ts"; +export type { HttpErrorOptions } from "https://deno.land/x/http_error@0.1.3/mod.ts"; diff --git a/errors.ts b/errors.ts index bcd6a71..5c473d3 100644 --- a/errors.ts +++ b/errors.ts @@ -1,11 +1,11 @@ import { HttpError, - HttpErrorInit, + HttpErrorOptions, isHttpError, optionsFromArgs, } from "./deps.ts"; -export interface OAuth2ErrorInit extends HttpErrorInit { +export interface OAuth2ErrorOptions extends HttpErrorOptions { /** An ASCII error code. */ code?: string; /** A URI identifying a human readable web page with information about the error. */ @@ -21,17 +21,17 @@ export class OAuth2Error extends HttpError { constructor( status?: number, message?: string, - options?: OAuth2ErrorInit, + options?: OAuth2ErrorOptions, ); - constructor(status?: number, options?: OAuth2ErrorInit); - constructor(message?: string, options?: OAuth2ErrorInit); - constructor(options?: OAuth2ErrorInit); + constructor(status?: number, options?: OAuth2ErrorOptions); + constructor(message?: string, options?: OAuth2ErrorOptions); + constructor(options?: OAuth2ErrorOptions); constructor( - statusOrMessageOrOptions?: number | string | OAuth2ErrorInit, - messageOrOptions?: string | OAuth2ErrorInit, - options?: OAuth2ErrorInit, + statusOrMessageOrOptions?: number | string | OAuth2ErrorOptions, + messageOrOptions?: string | OAuth2ErrorOptions, + options?: OAuth2ErrorOptions, ) { - const init: OAuth2ErrorInit = optionsFromArgs( + const init: OAuth2ErrorOptions = optionsFromArgs( statusOrMessageOrOptions, messageOrOptions, options, @@ -53,7 +53,7 @@ export function isOAuth2Error(value: unknown): value is OAuth2Error { * for authenticating the client, or is otherwise malformed. */ export class InvalidRequestError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "InvalidRequestError", @@ -66,7 +66,7 @@ export class InvalidRequestError extends OAuth2Error { /** Client authentication failed. */ export class InvalidClientError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "InvalidClientError", @@ -82,7 +82,7 @@ export class InvalidClientError extends OAuth2Error { * does not match the redirection URI used in the authorization request, or was issued to another client. */ export class InvalidGrantError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "InvalidGrantError", @@ -95,20 +95,33 @@ export class InvalidGrantError extends OAuth2Error { /** The authenticated client is not authorized to use this authorization grant type. */ export class UnauthorizedClientError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "UnauthorizedClientError", code: "unauthorized_client", status: 401, cause: options?.cause, - } as OAuth2ErrorInit); + } as OAuth2ErrorOptions); + } +} + +/** The token type is not supported by the authorization server. */ +export class UnsupportedTokenTypeError extends OAuth2Error { + constructor(message?: string, options?: ErrorOptions) { + super({ + message, + name: "UnsupportedTokenTypeError", + code: "unsupported_token_type", + status: 400, + cause: options?.cause, + }); } } /** The authorization grant type is not supported by the authorization server. */ export class UnsupportedGrantTypeError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "UnsupportedGrantTypeError", @@ -121,7 +134,7 @@ export class UnsupportedGrantTypeError extends OAuth2Error { /** The resource owner or authorization server denied the request. */ export class AccessDeniedError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "AccessDeniedError", @@ -137,7 +150,7 @@ export class AccessDeniedError extends OAuth2Error { * an authorization code using this method. */ export class UnsupportedResponseTypeError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "UnsupportedResponseTypeError", @@ -150,7 +163,7 @@ export class UnsupportedResponseTypeError extends OAuth2Error { /** The requested scope is invalid, unknown, or malformed. */ export class InvalidScopeError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "InvalidScopeError", @@ -166,7 +179,7 @@ export class InvalidScopeError extends OAuth2Error { * prevented it from fulfilling the request. */ export class ServerError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "ServerError", @@ -182,7 +195,7 @@ export class ServerError extends OAuth2Error { * a temporary overloading or maintenance of the server. */ export class TemporarilyUnavailableError extends OAuth2Error { - constructor(message?: string, options?: ErrorInit) { + constructor(message?: string, options?: ErrorOptions) { super({ message, name: "TemporarilyUnavailableError", diff --git a/errors_test.ts b/errors_test.ts index 006c25d..1afc0cc 100644 --- a/errors_test.ts +++ b/errors_test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertObjectMatch, test } from "./test_deps.ts"; +import { assertEquals, assertObjectMatch, it } from "./test_deps.ts"; import { AccessDeniedError, InvalidClientError, @@ -13,7 +13,7 @@ import { UnsupportedResponseTypeError, } from "./errors.ts"; -test("OAuth2Error", () => { +it("OAuth2Error", () => { class CustomError extends OAuth2Error {} assertObjectMatch(new CustomError(), { name: "OAuth2Error", @@ -46,7 +46,7 @@ test("OAuth2Error", () => { ); }); -test("InvalidRequestError", () => { +it("InvalidRequestError", () => { assertObjectMatch(new InvalidRequestError(), { name: "InvalidRequestError", status: 400, @@ -72,7 +72,7 @@ test("InvalidRequestError", () => { assertEquals(error.cause, cause); }); -test("InvalidClientError", () => { +it("InvalidClientError", () => { assertObjectMatch(new InvalidClientError(), { name: "InvalidClientError", status: 401, @@ -98,7 +98,7 @@ test("InvalidClientError", () => { assertEquals(error.cause, cause); }); -test("InvalidGrantError", () => { +it("InvalidGrantError", () => { assertObjectMatch(new InvalidGrantError(), { name: "InvalidGrantError", status: 400, @@ -124,7 +124,7 @@ test("InvalidGrantError", () => { assertEquals(error.cause, cause); }); -test("UnauthorizedClientError", () => { +it("UnauthorizedClientError", () => { assertObjectMatch(new UnauthorizedClientError(), { name: "UnauthorizedClientError", status: 401, @@ -150,7 +150,7 @@ test("UnauthorizedClientError", () => { assertEquals(error.cause, cause); }); -test("UnsupportedGrantTypeError", () => { +it("UnsupportedGrantTypeError", () => { assertObjectMatch(new UnsupportedGrantTypeError(), { name: "UnsupportedGrantTypeError", status: 400, @@ -176,7 +176,7 @@ test("UnsupportedGrantTypeError", () => { assertEquals(error.cause, cause); }); -test("AccessDeniedError", () => { +it("AccessDeniedError", () => { assertObjectMatch(new AccessDeniedError(), { name: "AccessDeniedError", status: 401, @@ -202,7 +202,7 @@ test("AccessDeniedError", () => { assertEquals(error.cause, cause); }); -test("UnsupportedResponseTypeError", () => { +it("UnsupportedResponseTypeError", () => { assertObjectMatch(new UnsupportedResponseTypeError(), { name: "UnsupportedResponseTypeError", status: 400, @@ -228,7 +228,7 @@ test("UnsupportedResponseTypeError", () => { assertEquals(error.cause, cause); }); -test("InvalidScopeError", () => { +it("InvalidScopeError", () => { assertObjectMatch(new InvalidScopeError(), { name: "InvalidScopeError", status: 400, @@ -254,7 +254,7 @@ test("InvalidScopeError", () => { assertEquals(error.cause, cause); }); -test("ServerError", () => { +it("ServerError", () => { assertObjectMatch(new ServerError(), { name: "ServerError", status: 500, @@ -280,7 +280,7 @@ test("ServerError", () => { assertEquals(error.cause, cause); }); -test("TemporarilyUnavailableError", () => { +it("TemporarilyUnavailableError", () => { assertObjectMatch(new TemporarilyUnavailableError(), { name: "TemporarilyUnavailableError", status: 503, diff --git a/examples/oak-localstorage/deps.ts b/examples/oak-localstorage/deps.ts index ef75ced..960377f 100644 --- a/examples/oak-localstorage/deps.ts +++ b/examples/oak-localstorage/deps.ts @@ -4,12 +4,12 @@ export { Cookies, Response, Router, -} from "https://deno.land/x/oak@v10.1.0/mod.ts"; -export type { BodyForm } from "https://deno.land/x/oak@v10.1.0/mod.ts"; +} from "https://deno.land/x/oak@v10.5.1/mod.ts"; +export type { BodyForm } from "https://deno.land/x/oak@v10.5.1/mod.ts"; export { encode as encodeBase64, -} from "https://deno.land/std@0.120.0/encoding/base64.ts"; +} from "https://deno.land/std@0.133.0/encoding/base64.ts"; export { AbstractAccessTokenService, @@ -30,6 +30,7 @@ export { RefreshTokenGrant, Scope, ServerError, + UnsupportedTokenTypeError, } from "../../authorization_server.ts"; export type { AccessToken, diff --git a/examples/oak-localstorage/main.ts b/examples/oak-localstorage/main.ts index 62eac82..96cfdd3 100644 --- a/examples/oak-localstorage/main.ts +++ b/examples/oak-localstorage/main.ts @@ -6,12 +6,15 @@ import { generateCodeVerifier, Response, Router, + Scope, + Token, TokenBody, } from "./deps.ts"; import { User } from "./models/user.ts"; import { oauth2, oauth2Router } from "./oauth2.ts"; import { Session } from "./models/session.ts"; -import { sessionService, userService } from "./services/mod.ts"; +import { sessionService, tokenService, userService } from "./services/mod.ts"; +import { Client } from "./models/client.ts"; const loginPage = (csrf: string, error?: string | null) => ` @@ -48,7 +51,8 @@ const router = new Router(); router .get("/", async (context) => { const { response } = context; - const token = await oauth2.getTokenForContext(context); + const token = await oauth2.getTokenForContext(context) + .catch(() => undefined); response.type = "html"; response.body = ` @@ -68,70 +72,124 @@ router }) .get("/login", async (context) => { const { request, response, cookies } = context; - const token = await oauth2.getTokenForContext(context); - if (token?.user) { - const redirectUri = request.url.searchParams.get("redirect_uri") ?? "/"; - response.redirect(redirectUri); + const redirectUri = request.url.searchParams.get("redirect_uri") ?? "/"; + let token: Token | undefined = undefined; + + const refreshToken = await cookies.get("refreshToken"); + if (refreshToken) { + const formParams = new URLSearchParams(); + formParams.set("client_id", "1000"); + formParams.set("grant_type", "refresh_token"); + formParams.set("refresh_token", refreshToken); + const now = Date.now(); + const headers = new Headers(); + headers.set("content-type", "application/x-www-form-urlencoded"); + headers.set("authorization", `basic ${btoa("1000:1234")}`); + const tokenResponse = await fetch("http://localhost:8000/oauth2/token", { + method: "POST", + headers, + body: formParams.toString(), + }); + const body = await tokenResponse.json(); + if (tokenResponse.status === 200) { + const { + access_token: accessToken, + refresh_token: refreshToken, + expires_in: expiresIn, + } = body as TokenBody; + if (accessToken) { + cookies.set("accessToken", accessToken, { + httpOnly: true, + expires: expiresIn ? new Date(now + (expiresIn * 1000)) : undefined, + }); + } + if (refreshToken) { + cookies.set("refreshToken", refreshToken, { + httpOnly: true, + path: "/login", + }); + cookies.set("refreshToken", refreshToken, { + httpOnly: true, + path: "/logout", + }); + cookies.set("refreshToken", refreshToken, { + httpOnly: true, + path: "/oauth2/token", + }); + } + token = await oauth2.getToken(accessToken) + .catch(() => undefined); + } else { + return showLogin(response, cookies); + } + } + + if (!token) { + token = await oauth2.getTokenForContext(context) + .catch(() => undefined); + } + + if (token) { + return response.redirect(redirectUri); } else { - showLogin(response, cookies); + return showLogin(response, cookies); } }) .post("/login", async ({ request, response, cookies }) => { const sessionId: string | undefined = await cookies.get("sessionId"); - let session: Session | undefined = undefined; - if (sessionId) { - session = await sessionService.get(sessionId); - await sessionService.delete(sessionId); - cookies.delete("sessionId"); - } + let session: Session | undefined = sessionId + ? await sessionService.get(sessionId) + : undefined; if (!session) { - showLogin( + return showLogin( response, cookies, new Error(`${sessionId ? "invalid" : "no"} session`), ); - } else { - try { - const body: BodyForm = request.body({ type: "form" }); - const form: URLSearchParams = await body.value; - const csrf = form.get("csrf"); - if (!csrf) throw new Error("csrf token required"); - if (csrf !== session.csrf) throw new Error("invalid csrf token"); - const username = form.get("username"); - if (!username) throw new Error("username required"); - const password = form.get("password"); - if (!password) throw new Error("password required"); - const user: User | undefined = await userService.getAuthenticated( - username, - password, - ); - if (!user) throw new Error("incorrect username or password"); - const redirectUri = request.url.searchParams.get("redirect_uri") ?? "/"; + } - session = await sessionService.start(); - await cookies.set("sessionId", session.id, { httpOnly: true }); - session.user = user; - session.state = crypto.randomUUID(); - session.redirectUri = redirectUri; - session.codeVerifier = generateCodeVerifier(); - await sessionService.patch(session); + try { + await sessionService.delete(sessionId!); + cookies.delete("sessionId"); + const body: BodyForm = request.body({ type: "form" }); + const form: URLSearchParams = await body.value; + const csrf = form.get("csrf"); + if (!csrf) throw new Error("csrf token required"); + if (csrf !== session.csrf) throw new Error("invalid csrf token"); + const username = form.get("username"); + if (!username) throw new Error("username required"); + const password = form.get("password"); + if (!password) throw new Error("password required"); + const user: User | undefined = await userService.getAuthenticated( + username, + password, + ); + if (!user) throw new Error("incorrect username or password"); + const redirectUri = request.url.searchParams.get("redirect_uri") ?? "/"; - const authorizeUrl = new URL("http://localhost:8000/oauth2/authorize"); - const { searchParams } = authorizeUrl; - searchParams.set("client_id", "1000"); - searchParams.set("response_type", "code"); - searchParams.set("state", session.state); - searchParams.set( - "code_challenge", - await challengeMethods.S256(session.codeVerifier), - ); - searchParams.set("code_challenge_method", "S256"); - searchParams.set("redirect_uri", "http://localhost:8000/cb"); - response.redirect(authorizeUrl); - } catch (error) { - showLogin(response, cookies, error); - } + session = await sessionService.start(); + await cookies.set("sessionId", session.id, { httpOnly: true }); + session.user = user; + session.state = crypto.randomUUID(); + session.redirectUri = redirectUri; + session.codeVerifier = generateCodeVerifier(); + await sessionService.patch(session); + + const authorizeUrl = new URL("http://localhost:8000/oauth2/authorize"); + const { searchParams } = authorizeUrl; + searchParams.set("client_id", "1000"); + searchParams.set("response_type", "code"); + searchParams.set("state", session.state); + searchParams.set( + "code_challenge", + await challengeMethods.S256(session.codeVerifier), + ); + searchParams.set("code_challenge_method", "S256"); + searchParams.set("redirect_uri", "http://localhost:8000/cb"); + response.redirect(authorizeUrl); + } catch (error) { + return showLogin(response, cookies, error); } }) .get("/cb", async ({ request, response, cookies }) => { @@ -150,7 +208,6 @@ router if ( code && state && state === expectedState && codeVerifier && redirectUri ) { - const tokenUrl = new URL("http://localhost:8000/oauth2/token"); const formParams = new URLSearchParams(); formParams.set("client_id", "1000"); formParams.set("grant_type", "authorization_code"); @@ -160,11 +217,14 @@ router const now = Date.now(); const headers = new Headers(); headers.set("content-type", "application/x-www-form-urlencoded"); - const tokenResponse = await fetch(tokenUrl, { - method: "POST", - headers, - body: formParams.toString(), - }); + const tokenResponse = await fetch( + "http://localhost:8000/oauth2/token", + { + method: "POST", + headers, + body: formParams.toString(), + }, + ); const body = await tokenResponse.json(); if (tokenResponse.status === 200) { const { @@ -172,10 +232,27 @@ router refresh_token: refreshToken, expires_in: expiresIn, } = body as TokenBody; - if (accessToken) session.accessToken = accessToken; - if (refreshToken) session.refreshToken = refreshToken; - if (expiresIn) { - session.accessTokenExpiresAt = new Date(now + (expiresIn * 1000)); + if (accessToken) { + cookies.set("accessToken", accessToken, { + httpOnly: true, + expires: expiresIn + ? new Date(now + (expiresIn * 1000)) + : undefined, + }); + } + if (refreshToken) { + cookies.set("refreshToken", refreshToken, { + httpOnly: true, + path: "/login", + }); + cookies.set("refreshToken", refreshToken, { + httpOnly: true, + path: "/logout", + }); + cookies.set("refreshToken", refreshToken, { + httpOnly: true, + path: "/oauth2/token", + }); } session.user = null; session.state = null; @@ -194,11 +271,23 @@ router } }) .get("/logout", async ({ response, cookies }) => { - const sessionId: string | undefined = await cookies.get("sessionId"); + const sessionId = await cookies.get("sessionId"); if (sessionId) { await sessionService.delete(sessionId); cookies.delete("sessionId"); } + const accessToken = await cookies.get("accessToken"); + if (accessToken) { + await tokenService.revoke(accessToken, "access_token"); + cookies.delete("accessToken"); + } + const refreshToken = await cookies.get("refreshToken"); + if (refreshToken) { + await tokenService.revoke(refreshToken, "refresh_token"); + cookies.delete("refreshToken", { path: "/login" }); + cookies.delete("refreshToken", { path: "/logout" }); + cookies.delete("refreshToken", { path: "/oauth2/token" }); + } response.redirect("/"); }) .get("/public", ({ response }) => { diff --git a/examples/oak-localstorage/models/session.ts b/examples/oak-localstorage/models/session.ts index 2a9079e..9f0c4b0 100644 --- a/examples/oak-localstorage/models/session.ts +++ b/examples/oak-localstorage/models/session.ts @@ -7,7 +7,4 @@ export interface Session { state?: string | null; redirectUri?: string | null; codeVerifier?: string | null; - accessToken?: string | null; - refreshToken?: string | null; - accessTokenExpiresAt?: Date | null; } diff --git a/examples/oak-localstorage/oauth2.ts b/examples/oak-localstorage/oauth2.ts index 80f2a34..93c26b8 100644 --- a/examples/oak-localstorage/oauth2.ts +++ b/examples/oak-localstorage/oauth2.ts @@ -8,12 +8,9 @@ import { OakOAuth2AuthorizeRequest, OakOAuth2Request, OakOAuth2Response, - OAuth2Error, RefreshTokenGrant, Router, Scope, - ServerError, - TokenBody, } from "./deps.ts"; import { Client } from "./models/client.ts"; import { Session } from "./models/session.ts"; @@ -58,82 +55,13 @@ async function getSession( return session; } -async function refreshSession(session: Session): Promise { - if (!session.refreshToken) { - throw new ServerError("refreshSession called without refresh token"); - } - - const tokenUrl = new URL("http://localhost:8000/oauth2/token"); - const formParams = new URLSearchParams(); - formParams.set("grant_type", "refresh_token"); - formParams.set("refresh_token", session.refreshToken); - const headers = new Headers(); - headers.set("authorization", `basic ${btoa("1000:1234")}`); // should be environment variable - headers.set("content-type", "application/x-www-form-urlencoded"); - const now = Date.now(); - const tokenResponse = await fetch(tokenUrl, { - method: "POST", - headers, - body: formParams.toString(), - }); - const body = await tokenResponse.json(); - if (tokenResponse.status >= 400) { - if (tokenResponse.status !== 503) { - await sessionService.delete(session); - } - throw new OAuth2Error({ - status: tokenResponse.status, - message: body.error_description, - code: body.error, - uri: body.error_uri, - }); - } else if (tokenResponse.status !== 200) { - throw new ServerError("unexpected response from authorization server"); - } - - const { - access_token: accessToken, - refresh_token: refreshToken, - expires_in: expiresIn, - } = body as TokenBody; - if (accessToken) session.accessToken = accessToken; - if (refreshToken) session.refreshToken = refreshToken; - if (expiresIn) { - session.accessTokenExpiresAt = new Date(now + (expiresIn * 1000)); - } - await sessionService.patch(session); - - return session; -} - -/* -The refreshSessionPromises is being used as an in-memory lock on refreshing sessions. -This prevents duplicate refresh token requests from being sent at the same time. -This will not work if there are multiple resource server processes using the same sessions. -*/ -const refreshSessionPromises = new Map>(); - export const oauth2 = new OakAuthorizationServer({ server: oauth2Server, async getAccessToken( request: OakOAuth2Request, - requireRefresh = false, ): Promise { - let session = await getSession(request); - if (requireRefresh && session?.refreshToken) { - const { refreshToken } = session; - let startedRefresh = false; - if (!refreshSessionPromises.has(refreshToken)) { - startedRefresh = true; - refreshSessionPromises.set(refreshToken, refreshSession(session)); - } - try { - session = await refreshSessionPromises.get(refreshToken); - } finally { - if (startedRefresh) refreshSessionPromises.delete(refreshToken); - } - } - return session?.accessToken ?? null; + const accessToken = await request.cookies.get("accessToken"); + return accessToken ?? null; }, }); export const oauth2Router = new Router(); @@ -152,17 +80,17 @@ const setAuthorization = async ( } const { clientId } = request.authorizeParameters; + const token = await oauth2.getTokenForRequest(request) + .catch(() => undefined); + if (token) { + request.user = token.user; + } - if (clientId === "1000") { + if (!request.user && clientId === "1000") { const session = await getSession(request); if (session?.user) { request.user = session.user; } - } else { - const token = await oauth2.getTokenForRequest(request); - if (token) { - request.user = token.user; - } } }; diff --git a/examples/oak-localstorage/services/session.ts b/examples/oak-localstorage/services/session.ts index 2af3f20..1774649 100644 --- a/examples/oak-localstorage/services/session.ts +++ b/examples/oak-localstorage/services/session.ts @@ -9,9 +9,6 @@ interface SessionInternal { state?: string; redirectUri?: string; codeVerifier?: string; - accessToken?: string; - refreshToken?: string; - accessTokenExpiresAt?: number; } export function saveSession(sessionId: string, session: Session): void { @@ -32,20 +29,12 @@ export class SessionService { state, redirectUri, codeVerifier, - accessToken, - refreshToken, - accessTokenExpiresAt, csrf, } = session; const next: SessionInternal = { id, csrf }; if (state) next.state = state; if (redirectUri) next.redirectUri = redirectUri; if (codeVerifier) next.codeVerifier = codeVerifier; - if (accessToken) next.accessToken = accessToken; - if (accessTokenExpiresAt) { - next.accessTokenExpiresAt = accessTokenExpiresAt?.valueOf(); - } - if (refreshToken) next.refreshToken = refreshToken; if (user) next.userId = user.id; localStorage.setItem(`session:${id}`, JSON.stringify(next)); return await Promise.resolve(); @@ -58,9 +47,6 @@ export class SessionService { state, redirectUri, codeVerifier, - accessToken, - refreshToken, - accessTokenExpiresAt, csrf, } = session; const current = await this.getInternal(id); @@ -79,16 +65,6 @@ export class SessionService { if (codeVerifier) next.codeVerifier = codeVerifier; else if (codeVerifier === null) delete next.codeVerifier; - if (accessToken) next.accessToken = accessToken; - else if (accessToken === null) delete next.accessToken; - - if (accessTokenExpiresAt) { - next.accessTokenExpiresAt = accessTokenExpiresAt?.valueOf(); - } else if (accessTokenExpiresAt === null) delete next.accessTokenExpiresAt; - - if (refreshToken) next.refreshToken = refreshToken; - else if (refreshToken === null) delete next.refreshToken; - if (csrf) next.csrf = csrf; localStorage.setItem(`session:${id}`, JSON.stringify(next)); @@ -116,9 +92,6 @@ export class SessionService { state, redirectUri, codeVerifier, - accessToken, - refreshToken, - accessTokenExpiresAt, csrf, userId, } = internal; @@ -127,13 +100,8 @@ export class SessionService { state, redirectUri, codeVerifier, - accessToken, - refreshToken, csrf, }; - if (accessTokenExpiresAt) { - external.accessTokenExpiresAt = new Date(accessTokenExpiresAt); - } if (userId) external.user = await this.userService.get(userId); return external; } diff --git a/examples/oak-localstorage/services/token.ts b/examples/oak-localstorage/services/token.ts index 13f8826..67cb002 100644 --- a/examples/oak-localstorage/services/token.ts +++ b/examples/oak-localstorage/services/token.ts @@ -3,6 +3,7 @@ import { RefreshToken, Scope, Token, + UnsupportedTokenTypeError, } from "../deps.ts"; import { Client } from "../models/client.ts"; import { User } from "../models/user.ts"; @@ -184,11 +185,17 @@ export class TokenService } async delete( - token: Token | TokenInternal, + token: string | Token | TokenInternal, ): Promise { - let existed = await this.deleteAccessToken(token.accessToken); - if (token.refreshToken) { - existed = await this.deleteRefreshToken(token.refreshToken) || existed; + let existed = false; + if (typeof token === "string") { + existed = await this.deleteAccessToken(token); + existed = await this.deleteRefreshToken(token) || existed; + } else { + existed = await this.deleteAccessToken(token.accessToken); + if (token.refreshToken) { + existed = await this.deleteRefreshToken(token.refreshToken) || existed; + } } return existed; } @@ -267,7 +274,21 @@ export class TokenService return (await this.getToken(token.accessToken))!; } - async revoke(token: Token): Promise { + async revoke(token: Token): Promise; + async revoke(token: string, hint?: string | null): Promise; + async revoke( + token: string | Token, + hint?: string | null, + ): Promise { + if (typeof token === "string") { + if (hint === "access_token") { + await this.deleteAccessToken(token); + } else if (hint === "refresh_token") { + await this.deleteRefreshToken(token); + } else { + throw new UnsupportedTokenTypeError("invalid token_type_hint"); + } + } return await this.delete(token); } diff --git a/grants/authorization_code_test.ts b/grants/authorization_code_test.ts index 5e53048..017d2e6 100644 --- a/grants/authorization_code_test.ts +++ b/grants/authorization_code_test.ts @@ -12,13 +12,12 @@ import { assertSpyCall, assertSpyCalls, assertStrictEquals, + describe, + it, Spy, spy, - SpyCall, Stub, stub, - test, - TestSuite, } from "../test_deps.ts"; import { InvalidClientError, @@ -46,7 +45,7 @@ import { } from "../services/test_services.ts"; import { User } from "../models/user.ts"; -const authorizationCodeGrantTests: TestSuite = new TestSuite({ +const authorizationCodeGrantTests = describe({ name: "AuthorizationCodeGrant", }); @@ -70,12 +69,12 @@ const services: AuthorizationCodeGrantServices = { }; const authorizationCodeGrant = new AuthorizationCodeGrant({ services }); -const getClientCredentialsTests: TestSuite = new TestSuite({ +const getClientCredentialsTests = describe({ name: "getClientCredentials", suite: authorizationCodeGrantTests, }); -test( +it( getClientCredentialsTests, "from request body without secret", async () => { @@ -88,7 +87,7 @@ test( }, ); -test(getClientCredentialsTests, "from request body with secret", async () => { +it(getClientCredentialsTests, "from request body with secret", async () => { const request = fakeTokenRequest( "client_id=1&client_secret=2", ); @@ -99,7 +98,7 @@ test(getClientCredentialsTests, "from request body with secret", async () => { assertEquals(await result, { clientId: "1", clientSecret: "2" }); }); -test( +it( getClientCredentialsTests, "from request body with code verifier", async () => { @@ -114,12 +113,12 @@ test( }, ); -const getAuthenticatedClientTests: TestSuite = new TestSuite({ +const getAuthenticatedClientTests = describe({ name: "getAuthenticatedClient", suite: authorizationCodeGrantTests, }); -test(getAuthenticatedClientTests, "getClientCredentials failed", async () => { +it(getAuthenticatedClientTests, "getClientCredentials failed", async () => { const getClientCredentials = spy( authorizationCodeGrant, "getClientCredentials", @@ -153,7 +152,7 @@ test(getAuthenticatedClientTests, "getClientCredentials failed", async () => { } }); -test( +it( getAuthenticatedClientTests, "client authentication failed without secret", async () => { @@ -197,7 +196,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "client authentication failed with secret", async () => { @@ -241,7 +240,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "client authentication failed with PKCE", async () => { @@ -291,7 +290,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "returns client authenticated without secret", async () => { @@ -320,10 +319,11 @@ test( assertSpyCalls(clientServiceGet, 0); - const call: SpyCall = assertSpyCall(clientServiceGetAuthenticated, 0, { + assertSpyCall(clientServiceGetAuthenticated, 0, { self: clientService, args: ["1"], }); + const call = clientServiceGetAuthenticated.calls[0]; assertSpyCalls(clientServiceGetAuthenticated, 1); assertEquals(client, await call.returned); @@ -335,7 +335,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "returns client authenticated with secret", async () => { @@ -364,10 +364,11 @@ test( assertSpyCalls(clientServiceGet, 0); - const call: SpyCall = assertSpyCall(clientServiceGetAuthenticated, 0, { + assertSpyCall(clientServiceGetAuthenticated, 0, { self: clientService, args: ["1", "2"], }); + const call = clientServiceGetAuthenticated.calls[0]; assertSpyCalls(clientServiceGetAuthenticated, 1); assertEquals(client, await call.returned); @@ -379,7 +380,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "returns client authenticated with PKCE", async () => { @@ -408,10 +409,11 @@ test( }); assertSpyCalls(getClientCredentials, 1); - const call = assertSpyCall(clientServiceGet, 0, { + assertSpyCall(clientServiceGet, 0, { self: clientService, args: ["1"], }); + const call = clientServiceGet.calls[0]; assertSpyCalls(clientServiceGet, 1); assertSpyCalls(clientServiceGetAuthenticated, 0); @@ -425,12 +427,12 @@ test( }, ); -const getChallengeMethodTests: TestSuite = new TestSuite({ +const getChallengeMethodTests = describe({ name: "getChallengeMethod", suite: authorizationCodeGrantTests, }); -test(getChallengeMethodTests, "with default challenge methods", () => { +it(getChallengeMethodTests, "with default challenge methods", () => { assertStrictEquals(authorizationCodeGrant.getChallengeMethod(), undefined); assertStrictEquals( authorizationCodeGrant.getChallengeMethod("plain"), @@ -450,7 +452,7 @@ test(getChallengeMethodTests, "with default challenge methods", () => { ); }); -test(getChallengeMethodTests, "with custom challenge methods", () => { +it(getChallengeMethodTests, "with custom challenge methods", () => { const plain: ChallengeMethod = (verifier: string) => Promise.resolve(verifier); const other: ChallengeMethod = (verifier: string) => @@ -469,12 +471,12 @@ test(getChallengeMethodTests, "with custom challenge methods", () => { ); }); -const validateChallengeMethodTests: TestSuite = new TestSuite({ +const validateChallengeMethodTests = describe({ name: "validateChallengeMethod", suite: authorizationCodeGrantTests, }); -test(validateChallengeMethodTests, "with default challenge methods", () => { +it(validateChallengeMethodTests, "with default challenge methods", () => { assertStrictEquals(authorizationCodeGrant.validateChallengeMethod(), false); assertStrictEquals( authorizationCodeGrant.validateChallengeMethod("plain"), @@ -494,7 +496,7 @@ test(validateChallengeMethodTests, "with default challenge methods", () => { ); }); -test(validateChallengeMethodTests, "with custom challenge methods", () => { +it(validateChallengeMethodTests, "with custom challenge methods", () => { const plain: ChallengeMethod = (verifier: string) => Promise.resolve(verifier); const other: ChallengeMethod = (verifier: string) => @@ -516,12 +518,12 @@ interface VerifyCodeContext { authorizationCode: AuthorizationCode; } -const verifyCodeTests: TestSuite = new TestSuite({ +const verifyCodeTests = describe({ name: "verifyCode", suite: authorizationCodeGrantTests, - async beforeEach(context: VerifyCodeContext) { - context.codeVerifier = generateCodeVerifier(); - context.authorizationCode = { + async beforeEach() { + this.codeVerifier = generateCodeVerifier(); + this.authorizationCode = { code: "123", expiresAt: new Date(Date.now() + 60000), redirectUri: "https://client.example.com/cb", @@ -529,15 +531,16 @@ const verifyCodeTests: TestSuite = new TestSuite({ user, scope, challengeMethod: "S256", - challenge: await challengeMethods["S256"](context.codeVerifier), + challenge: await challengeMethods["S256"](this.codeVerifier), }; }, }); -test( +it( verifyCodeTests, "returns false if code has no challenge", - async ({ codeVerifier, authorizationCode }) => { + async function () { + const { codeVerifier, authorizationCode } = this; delete authorizationCode.challenge; delete authorizationCode.challengeMethod; assertStrictEquals( @@ -547,22 +550,26 @@ test( }, ); -test( +it( verifyCodeTests, "returns false if verifier is incorrect", - async ({ authorizationCode }) => { + async function () { const codeVerifier: string = generateCodeVerifier(); assertStrictEquals( - await authorizationCodeGrant.verifyCode(authorizationCode, codeVerifier), + await authorizationCodeGrant.verifyCode( + this.authorizationCode, + codeVerifier, + ), false, ); }, ); -test( +it( verifyCodeTests, "returns true if verifier is correct", - async ({ codeVerifier, authorizationCode }) => { + async function () { + const { codeVerifier, authorizationCode } = this; assertStrictEquals( await authorizationCodeGrant.verifyCode(authorizationCode, codeVerifier), true, @@ -570,10 +577,11 @@ test( }, ); -test( +it( verifyCodeTests, "throws if challenge method is not implemented", - async ({ codeVerifier, authorizationCode }) => { + async function () { + const { codeVerifier, authorizationCode } = this; delete authorizationCode.challengeMethod; await assertRejects( () => authorizationCodeGrant.verifyCode(authorizationCode, codeVerifier), @@ -589,12 +597,12 @@ test( }, ); -const tokenTests: TestSuite = new TestSuite({ +const tokenTests = describe({ name: "token", suite: authorizationCodeGrantTests, }); -test(tokenTests, "code parameter required", async () => { +it(tokenTests, "code parameter required", async () => { let request = fakeTokenRequest(""); const result = authorizationCodeGrant.token( request, @@ -615,7 +623,7 @@ test(tokenTests, "code parameter required", async () => { ); }); -test(tokenTests, "code already used", async () => { +it(tokenTests, "code already used", async () => { const revokeCode: Stub = stub( tokenService, "revokeCode", @@ -644,7 +652,7 @@ test(tokenTests, "code already used", async () => { } }); -test(tokenTests, "invalid code", async () => { +it(tokenTests, "invalid code", async () => { const get: Stub = stub( authorizationCodeService, "get", @@ -673,7 +681,7 @@ test(tokenTests, "invalid code", async () => { } }); -test(tokenTests, "expired code", async () => { +it(tokenTests, "expired code", async () => { const originalGet = authorizationCodeService.get; const get: Stub = stub( authorizationCodeService, @@ -706,7 +714,7 @@ test(tokenTests, "expired code", async () => { } }); -test(tokenTests, "code was issued to another client", async () => { +it(tokenTests, "code was issued to another client", async () => { const originalGet = authorizationCodeService.get; const get: Stub = stub( authorizationCodeService, @@ -739,7 +747,7 @@ test(tokenTests, "code was issued to another client", async () => { } }); -test(tokenTests, "redirect_uri parameter required", async () => { +it(tokenTests, "redirect_uri parameter required", async () => { const get: Spy = spy( authorizationCodeService, "get", @@ -780,7 +788,7 @@ test(tokenTests, "redirect_uri parameter required", async () => { } }); -test(tokenTests, "incorrect redirect_uri", async () => { +it(tokenTests, "incorrect redirect_uri", async () => { const get: Spy = spy( authorizationCodeService, "get", @@ -829,7 +837,7 @@ test(tokenTests, "incorrect redirect_uri", async () => { } }); -test(tokenTests, "did not expect redirect_uri parameter", async () => { +it(tokenTests, "did not expect redirect_uri parameter", async () => { const originalGet = authorizationCodeService.get; const get: Stub = stub( authorizationCodeService, @@ -866,7 +874,7 @@ test(tokenTests, "did not expect redirect_uri parameter", async () => { } }); -test( +it( tokenTests, "client authentication failed with PKCE because of incorrect code_verifier", async () => { @@ -916,7 +924,7 @@ test( }, ); -test( +it( tokenTests, "client authentication failed with PKCE because of missing code_verifier", async () => { @@ -964,7 +972,7 @@ test( }, ); -test(tokenTests, "returns token", async () => { +it(tokenTests, "returns token", async () => { const get = spy( authorizationCodeService, "get", @@ -999,13 +1007,14 @@ test(tokenTests, "returns token", async () => { assertStrictEquals(Promise.resolve(result), result); const token = await result; - const call: SpyCall = assertSpyCall(get, 0, { + assertSpyCall(get, 0, { self: authorizationCodeService, args: ["1"], }); + const call = get.calls[0]; assertSpyCalls(get, 1); - const { user, scope }: AuthorizationCode = await call - .returned; + const { user, scope } = await call + .returned as AuthorizationCode; assertClientUserScopeCall( generateToken, @@ -1041,7 +1050,7 @@ test(tokenTests, "returns token", async () => { } }); -test( +it( tokenTests, "returns token using client authenticated with PKCE", async () => { @@ -1090,13 +1099,14 @@ test( assertStrictEquals(Promise.resolve(result), result); const token = await result; - const call: SpyCall = assertSpyCall(get, 0, { + assertSpyCall(get, 0, { self: authorizationCodeService, args: ["1"], }); + const call = get.calls[0]; assertSpyCalls(get, 1); - const { user, scope }: AuthorizationCode = await call - .returned; + const { user, scope } = await call + .returned as AuthorizationCode; assertClientUserScopeCall( generateToken, @@ -1133,12 +1143,12 @@ test( }, ); -const generateAuthorizationCodeTests: TestSuite = new TestSuite({ +const generateAuthorizationCodeTests = describe({ name: "generateAuthorizationCode", suite: authorizationCodeGrantTests, }); -test(generateAuthorizationCodeTests, "generateCode error", async () => { +it(generateAuthorizationCodeTests, "generateCode error", async () => { const generateCode: Stub = stub( authorizationCodeService, "generateCode", @@ -1179,7 +1189,7 @@ test(generateAuthorizationCodeTests, "generateCode error", async () => { } }); -test(generateAuthorizationCodeTests, "expiresAt error", async () => { +it(generateAuthorizationCodeTests, "expiresAt error", async () => { const generateCode: Spy = spy( authorizationCodeService, "generateCode", @@ -1227,7 +1237,7 @@ test(generateAuthorizationCodeTests, "expiresAt error", async () => { } }); -test(generateAuthorizationCodeTests, "save error", async () => { +it(generateAuthorizationCodeTests, "save error", async () => { const generateCode: Stub = stub( authorizationCodeService, "generateCode", @@ -1270,9 +1280,10 @@ test(generateAuthorizationCodeTests, "save error", async () => { user, ); assertSpyCalls(expiresAt, 1); - const call: SpyCall = assertSpyCall(save, 0, { + assertSpyCall(save, 0, { self: authorizationCodeService, }); + const call = save.calls[0]; assertEquals(call.args.length, 1); assertAuthorizationCode(call.args[0], { code: "1", @@ -1288,7 +1299,7 @@ test(generateAuthorizationCodeTests, "save error", async () => { } }); -test( +it( generateAuthorizationCodeTests, "without optional properties", async () => { @@ -1337,9 +1348,10 @@ test( user, ); assertSpyCalls(expiresAt, 1); - const call: SpyCall = assertSpyCall(save, 0, { + assertSpyCall(save, 0, { self: authorizationCodeService, }); + const call = save.calls[0]; assertEquals(call.args.length, 1); assertAuthorizationCode(call.args[0], expectedAuthorizationCode); assertSpyCalls(save, 1); @@ -1351,7 +1363,7 @@ test( }, ); -test(generateAuthorizationCodeTests, "with optional properties", async () => { +it(generateAuthorizationCodeTests, "with optional properties", async () => { const generateCode: Stub = stub( authorizationCodeService, "generateCode", @@ -1409,9 +1421,10 @@ test(generateAuthorizationCodeTests, "with optional properties", async () => { scope, ); assertSpyCalls(expiresAt, 1); - const call: SpyCall = assertSpyCall(save, 0, { + assertSpyCall(save, 0, { self: authorizationCodeService, }); + const call = save.calls[0]; assertEquals(call.args.length, 1); assertAuthorizationCode(call.args[0], expectedAuthorizationCode); assertSpyCalls(save, 1); diff --git a/grants/client_credentials_test.ts b/grants/client_credentials_test.ts index ed3f1b2..e348668 100644 --- a/grants/client_credentials_test.ts +++ b/grants/client_credentials_test.ts @@ -10,13 +10,12 @@ import { assertRejects, assertSpyCalls, assertStrictEquals, + describe, + it, Spy, spy, - SpyCall, Stub, stub, - test, - TestSuite, } from "../test_deps.ts"; import { InvalidGrantError, @@ -33,11 +32,9 @@ import { } from "../services/test_services.ts"; import { User } from "../models/user.ts"; -const clientCredentialsGrantTests: TestSuite = new TestSuite({ - name: "ClientCredentialsGrant", -}); +const clientCredentialsGrantTests = describe("ClientCredentialsGrant"); -const tokenTests: TestSuite = new TestSuite({ +const tokenTests = describe({ name: "token", suite: clientCredentialsGrantTests, }); @@ -56,7 +53,7 @@ const services: ClientCredentialsGrantServices = { }; const clientCredentialsGrant = new ClientCredentialsGrant({ services }); -test(tokenTests, "not implemented for UserService", async () => { +it(tokenTests, "not implemented for UserService", async () => { const getUser: Spy = spy( clientService, "getUser", @@ -74,7 +71,7 @@ test(tokenTests, "not implemented for UserService", async () => { "clientService.getUser not implemented", ); assertStrictEquals(getUser.calls.length, 1); - const call: SpyCall = getUser.calls[0]; + const call = getUser.calls[0]; assertStrictEquals(call.self, clientService); assertEquals(call.args.length, 1); assertStrictEquals(call.args[0], client); @@ -83,7 +80,7 @@ test(tokenTests, "not implemented for UserService", async () => { } }); -test(tokenTests, "invalid scope", async () => { +it(tokenTests, "invalid scope", async () => { const acceptedScope = spy(clientCredentialsGrant, "acceptedScope"); try { let request = fakeTokenRequest("scope=\\"); @@ -109,7 +106,7 @@ test(tokenTests, "invalid scope", async () => { } }); -test(tokenTests, "no user for client", async () => { +it(tokenTests, "no user for client", async () => { const getUser: Stub = stub( clientService, "getUser", @@ -128,7 +125,7 @@ test(tokenTests, "no user for client", async () => { "no user for client", ); assertStrictEquals(getUser.calls.length, 1); - const call: SpyCall = getUser.calls[0]; + const call = getUser.calls[0]; assertStrictEquals(call.self, clientService); assertEquals(call.args.length, 1); assertStrictEquals(call.args[0], client); @@ -137,7 +134,7 @@ test(tokenTests, "no user for client", async () => { } }); -test(tokenTests, "scope not accepted", async () => { +it(tokenTests, "scope not accepted", async () => { const user = { username: "kyle" }; const getUser = stub( clientService, @@ -177,7 +174,7 @@ test(tokenTests, "scope not accepted", async () => { } }); -test(tokenTests, "returns accessToken", async () => { +it(tokenTests, "returns accessToken", async () => { const username = "kyle"; const getUser = stub( clientService, @@ -215,11 +212,11 @@ test(tokenTests, "returns accessToken", async () => { const token = await result; assertStrictEquals(getUser.calls.length, 1); - let call: SpyCall = getUser.calls[0]; - assertStrictEquals(call.self, clientService); - assertEquals(call.args.length, 1); - assertStrictEquals(call.args[0], client); - const user: User = await call.returned; + const getCall = getUser.calls[0]; + assertStrictEquals(getCall.self, clientService); + assertEquals(getCall.args.length, 1); + assertStrictEquals(getCall.args[0], client); + const user = await getCall.returned; assertClientUserScopeCall( acceptedScope, @@ -245,13 +242,13 @@ test(tokenTests, "returns accessToken", async () => { accessToken: "x", accessTokenExpiresAt, client, - user, + user: user!, scope: expectedScope, }; assertStrictEquals(save.calls.length, 1); - call = save.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], expectedToken); + const saveCall = save.calls[0]; + assertStrictEquals(saveCall.args.length, 1); + assertToken(saveCall.args[0], expectedToken); assertToken(token, expectedToken); } finally { getUser.restore(); diff --git a/grants/grant_test.ts b/grants/grant_test.ts index f1a53c8..7630cf8 100644 --- a/grants/grant_test.ts +++ b/grants/grant_test.ts @@ -13,13 +13,12 @@ import { assertRejects, assertSpyCalls, assertStrictEquals, + describe, + it, Spy, spy, - SpyCall, Stub, stub, - test, - TestSuite, } from "../test_deps.ts"; import { fakeTokenRequest } from "../test_context.ts"; import { InvalidClientError, InvalidScopeError } from "../errors.ts"; @@ -30,7 +29,7 @@ import { } from "../services/test_services.ts"; import { User } from "../models/user.ts"; -const grantTests: TestSuite = new TestSuite({ name: "Grant" }); +const grantTests = describe("Grant"); const clientService = new ClientService(); const client: Client = (clientService as ClientService).client; @@ -53,7 +52,7 @@ const refreshTokenGrant: ExampleGrant = new ExampleGrant({ allowRefreshToken: true, }); -test(grantTests, "parseScope", () => { +it(grantTests, "parseScope", () => { assertScope(grant.parseScope(undefined), undefined); assertScope(grant.parseScope(null), undefined); assertScope(grant.parseScope(""), undefined); @@ -61,12 +60,12 @@ test(grantTests, "parseScope", () => { assertScope(grant.parseScope("read write"), new Scope("read write")); }); -const acceptedScopeTests: TestSuite = new TestSuite({ +const acceptedScopeTests = describe({ name: "acceptedScope", suite: grantTests, }); -test( +it( acceptedScopeTests, "returns undefined if token service accepts no scope", async () => { @@ -93,7 +92,7 @@ test( }, ); -test( +it( acceptedScopeTests, "returns scope accepted by token service without requesting scope", async () => { @@ -114,7 +113,7 @@ test( }, ); -test( +it( acceptedScopeTests, "returns scope accepted by token service instead of requested scope", async () => { @@ -145,7 +144,7 @@ test( }, ); -test(acceptedScopeTests, "invalid scope", async () => { +it(acceptedScopeTests, "invalid scope", async () => { const acceptedScope = stub( tokenService, "acceptedScope", @@ -172,7 +171,7 @@ test(acceptedScopeTests, "invalid scope", async () => { } }); -test(acceptedScopeTests, "scope required", async () => { +it(acceptedScopeTests, "scope required", async () => { const acceptedScope = stub( tokenService, "acceptedScope", @@ -192,12 +191,12 @@ test(acceptedScopeTests, "scope required", async () => { } }); -const getClientCredentialsTests: TestSuite = new TestSuite({ +const getClientCredentialsTests = describe({ name: "getClientCredentials", suite: grantTests, }); -test( +it( getClientCredentialsTests, "authorization header required if credentials not in body", async () => { @@ -211,7 +210,7 @@ test( }, ); -test( +it( getClientCredentialsTests, "from request body without secret", async () => { @@ -223,7 +222,7 @@ test( }, ); -test(getClientCredentialsTests, "from request body with secret", async () => { +it(getClientCredentialsTests, "from request body with secret", async () => { const request = fakeTokenRequest( "client_id=1&client_secret=2", ); @@ -233,7 +232,7 @@ test(getClientCredentialsTests, "from request body with secret", async () => { assertEquals(await result, { clientId: "1", clientSecret: "2" }); }); -test( +it( getClientCredentialsTests, "from authorization header without secret", async () => { @@ -245,7 +244,7 @@ test( }, ); -test( +it( getClientCredentialsTests, "from authorization header with secret", async () => { @@ -257,7 +256,7 @@ test( }, ); -test( +it( getClientCredentialsTests, "ignores request body when authorization header is present", async () => { @@ -271,12 +270,12 @@ test( }, ); -const getAuthenticatedClientTests: TestSuite = new TestSuite({ +const getAuthenticatedClientTests = describe({ name: "getAuthenticatedClient", suite: grantTests, }); -test(getAuthenticatedClientTests, "getClientCredentials failed", async () => { +it(getAuthenticatedClientTests, "getClientCredentials failed", async () => { const getClientCredentials: Spy = spy( grant, "getClientCredentials", @@ -296,7 +295,7 @@ test(getAuthenticatedClientTests, "getClientCredentials failed", async () => { ); assertEquals(getClientCredentials.calls.length, 1); - const call: SpyCall = getClientCredentials.calls[0]; + const call = getClientCredentials.calls[0]; assertEquals(call.args.length, 1); assertStrictEquals(call.args[0], request); assertStrictEquals(call.self, grant); @@ -308,7 +307,7 @@ test(getAuthenticatedClientTests, "getClientCredentials failed", async () => { } }); -test( +it( getAuthenticatedClientTests, "client authentication failed without secret", async () => { @@ -331,15 +330,15 @@ test( ); assertEquals(getClientCredentials.calls.length, 1); - let call: SpyCall = getClientCredentials.calls[0]; - assertEquals(call.args.length, 1); - assertStrictEquals(call.args[0], request); - assertStrictEquals(call.self, grant); + const getClientCredentialsCall = getClientCredentials.calls[0]; + assertEquals(getClientCredentialsCall.args.length, 1); + assertStrictEquals(getClientCredentialsCall.args[0], request); + assertStrictEquals(getClientCredentialsCall.self, grant); assertEquals(clientServiceGetAuthenticated.calls.length, 1); - call = clientServiceGetAuthenticated.calls[0]; - assertEquals(call.args, ["1"]); - assertStrictEquals(call.self, clientService); + const getAuthenticatedCall = clientServiceGetAuthenticated.calls[0]; + assertEquals(getAuthenticatedCall.args, ["1"]); + assertStrictEquals(getAuthenticatedCall.self, clientService); } finally { getClientCredentials.restore(); clientServiceGetAuthenticated.restore(); @@ -347,7 +346,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "client authentication failed with secret", async () => { @@ -370,15 +369,15 @@ test( ); assertEquals(getClientCredentials.calls.length, 1); - let call: SpyCall = getClientCredentials.calls[0]; - assertEquals(call.args.length, 1); - assertStrictEquals(call.args[0], request); - assertStrictEquals(call.self, grant); + const getClientCredentialsCall = getClientCredentials.calls[0]; + assertEquals(getClientCredentialsCall.args.length, 1); + assertStrictEquals(getClientCredentialsCall.args[0], request); + assertStrictEquals(getClientCredentialsCall.self, grant); assertEquals(clientServiceGetAuthenticated.calls.length, 1); - call = clientServiceGetAuthenticated.calls[0]; - assertEquals(call.args, ["1", "2"]); - assertStrictEquals(call.self, clientService); + const getAuthenticatedCall = clientServiceGetAuthenticated.calls[0]; + assertEquals(getAuthenticatedCall.args, ["1", "2"]); + assertStrictEquals(getAuthenticatedCall.self, clientService); } finally { getClientCredentials.restore(); clientServiceGetAuthenticated.restore(); @@ -386,7 +385,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "returns client authenticated without secret", async () => { @@ -406,17 +405,17 @@ test( const client = await result; assertEquals(getClientCredentials.calls.length, 1); - let call: SpyCall = getClientCredentials.calls[0]; - assertEquals(call.args.length, 1); - assertStrictEquals(call.args[0], request); - assertStrictEquals(call.self, grant); + const getClientCredentialsCall = getClientCredentials.calls[0]; + assertEquals(getClientCredentialsCall.args.length, 1); + assertStrictEquals(getClientCredentialsCall.args[0], request); + assertStrictEquals(getClientCredentialsCall.self, grant); assertEquals(clientServiceGetAuthenticated.calls.length, 1); - call = clientServiceGetAuthenticated.calls[0]; - assertEquals(call.args, ["1"]); - assertStrictEquals(call.self, clientService); + const getAuthenticatedCall = clientServiceGetAuthenticated.calls[0]; + assertEquals(getAuthenticatedCall.args, ["1"]); + assertStrictEquals(getAuthenticatedCall.self, clientService); - assertEquals(client, await call.returned); + assertEquals(client, await getAuthenticatedCall.returned); } finally { getClientCredentials.restore(); clientServiceGetAuthenticated.restore(); @@ -424,7 +423,7 @@ test( }, ); -test( +it( getAuthenticatedClientTests, "returns client authenticated with secret", async () => { @@ -444,17 +443,17 @@ test( const client = await result; assertEquals(getClientCredentials.calls.length, 1); - let call: SpyCall = getClientCredentials.calls[0]; - assertEquals(call.args.length, 1); - assertStrictEquals(call.args[0], request); - assertStrictEquals(call.self, grant); + const getClientCredentialsCall = getClientCredentials.calls[0]; + assertEquals(getClientCredentialsCall.args.length, 1); + assertStrictEquals(getClientCredentialsCall.args[0], request); + assertStrictEquals(getClientCredentialsCall.self, grant); assertEquals(clientServiceGetAuthenticated.calls.length, 1); - call = clientServiceGetAuthenticated.calls[0]; - assertEquals(call.args, ["1", "2"]); - assertStrictEquals(call.self, clientService); + const getAuthenticatedCall = clientServiceGetAuthenticated.calls[0]; + assertEquals(getAuthenticatedCall.args, ["1", "2"]); + assertStrictEquals(getAuthenticatedCall.self, clientService); - assertEquals(client, await call.returned); + assertEquals(client, await getAuthenticatedCall.returned); } finally { getClientCredentials.restore(); clientServiceGetAuthenticated.restore(); @@ -462,14 +461,14 @@ test( }, ); -const generateTokenTests: TestSuite = new TestSuite({ +const generateTokenTests = describe({ name: "generateToken", suite: grantTests, }); const user: User = { username: "kyle" }; -test( +it( generateTokenTests, "access token without optional properties", async () => { @@ -509,7 +508,7 @@ test( }, ); -test(generateTokenTests, "access token with optional properties", async () => { +it(generateTokenTests, "access token with optional properties", async () => { const generateAccessToken: Stub = stub( tokenService, "generateAccessToken", @@ -549,7 +548,7 @@ test(generateTokenTests, "access token with optional properties", async () => { } }); -test( +it( generateTokenTests, "refresh token allowed without optional properties", async () => { @@ -601,7 +600,7 @@ test( }, ); -test( +it( generateTokenTests, "refresh token allowed with optional properties", async () => { diff --git a/grants/password_test.ts b/grants/password_test.ts index 7b7cda9..cc51fbc 100644 --- a/grants/password_test.ts +++ b/grants/password_test.ts @@ -7,13 +7,12 @@ import { assertRejects, assertSpyCalls, assertStrictEquals, + describe, + it, Spy, spy, - SpyCall, Stub, stub, - test, - TestSuite, } from "../test_deps.ts"; import { InvalidGrantError, @@ -31,11 +30,11 @@ import { } from "../services/test_services.ts"; import { User } from "../models/user.ts"; -const passwordGrantTests: TestSuite = new TestSuite({ +const passwordGrantTests = describe({ name: "PasswordGrant", }); -const tokenTests: TestSuite = new TestSuite({ +const tokenTests = describe({ name: "token", suite: passwordGrantTests, }); @@ -56,7 +55,7 @@ const services: PasswordGrantServices = { }; const passwordGrant = new PasswordGrant({ services }); -test(tokenTests, "not implemented for UserService", async () => { +it(tokenTests, "not implemented for UserService", async () => { const getAuthenticated: Spy = spy( userService, "getAuthenticated", @@ -76,7 +75,7 @@ test(tokenTests, "not implemented for UserService", async () => { "userService.getAuthenticated not implemented", ); assertStrictEquals(getAuthenticated.calls.length, 1); - let call: SpyCall = getAuthenticated.calls[0]; + let call = getAuthenticated.calls[0]; assertStrictEquals(call.self, userService); assertEquals(call.args, ["kyle", "hunter2"]); @@ -95,7 +94,7 @@ test(tokenTests, "not implemented for UserService", async () => { } }); -test(tokenTests, "invalid scope", async () => { +it(tokenTests, "invalid scope", async () => { let request = fakeTokenRequest("scope=\\"); const result = passwordGrant.token( request, @@ -116,7 +115,7 @@ test(tokenTests, "invalid scope", async () => { ); }); -test(tokenTests, "username parameter required", async () => { +it(tokenTests, "username parameter required", async () => { let request = fakeTokenRequest(""); const result = passwordGrant.token( request, @@ -137,7 +136,7 @@ test(tokenTests, "username parameter required", async () => { ); }); -test(tokenTests, "password parameter required", async () => { +it(tokenTests, "password parameter required", async () => { let request = fakeTokenRequest("username=kyle"); const result = passwordGrant.token( request, @@ -158,7 +157,7 @@ test(tokenTests, "password parameter required", async () => { ); }); -test(tokenTests, "user authentication failed", async () => { +it(tokenTests, "user authentication failed", async () => { const getAuthenticated: Stub = stub( userService, "getAuthenticated", @@ -179,7 +178,7 @@ test(tokenTests, "user authentication failed", async () => { "user authentication failed", ); assertStrictEquals(getAuthenticated.calls.length, 1); - const call: SpyCall = getAuthenticated.calls[0]; + const call = getAuthenticated.calls[0]; assertStrictEquals(call.self, userService); assertEquals(call.args, ["Kyle", "Hunter2"]); } finally { @@ -187,7 +186,7 @@ test(tokenTests, "user authentication failed", async () => { } }); -test(tokenTests, "scope not accepted", async () => { +it(tokenTests, "scope not accepted", async () => { const user = { username: "Kyle" }; const getAuthenticated: Stub = stub( userService, @@ -229,7 +228,7 @@ test(tokenTests, "scope not accepted", async () => { } }); -test(tokenTests, "returns token", async () => { +it(tokenTests, "returns token", async () => { const getAuthenticated = stub( userService, "getAuthenticated", @@ -270,10 +269,10 @@ test(tokenTests, "returns token", async () => { const token = await result; assertStrictEquals(getAuthenticated.calls.length, 1); - let call: SpyCall = getAuthenticated.calls[0]; - assertStrictEquals(call.self, userService); - assertEquals(call.args, ["Kyle", "Hunter2"]); - const user: User = await call.returned; + const getCall = getAuthenticated.calls[0]; + assertStrictEquals(getCall.self, userService); + assertEquals(getCall.args, ["Kyle", "Hunter2"]); + const user = await getCall.returned; assertClientUserScopeCall( acceptedScope, @@ -301,13 +300,13 @@ test(tokenTests, "returns token", async () => { accessTokenExpiresAt, refreshTokenExpiresAt, client, - user, + user: user!, scope: expectedScope, }; assertStrictEquals(save.calls.length, 1); - call = save.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], expectedToken); + const saveCall = save.calls[0]; + assertStrictEquals(saveCall.args.length, 1); + assertToken(saveCall.args[0], expectedToken); assertToken(token, expectedToken); } finally { getAuthenticated.restore(); diff --git a/grants/refresh_token_test.ts b/grants/refresh_token_test.ts index 3720071..a89a0e0 100644 --- a/grants/refresh_token_test.ts +++ b/grants/refresh_token_test.ts @@ -8,12 +8,11 @@ import { assertSpyCall, assertSpyCalls, assertStrictEquals, + describe, + it, spy, - SpyCall, Stub, stub, - test, - TestSuite, } from "../test_deps.ts"; import { AccessTokenService, @@ -31,18 +30,16 @@ import { fakeTokenRequest } from "../test_context.ts"; import { assertClientUserScopeCall, assertToken } from "../asserts.ts"; import { User } from "../models/user.ts"; -const refreshTokenGrantTests: TestSuite = new TestSuite({ - name: "RefreshTokenGrant", -}); +const refreshTokenGrantTests = describe("RefreshTokenGrant"); -const tokenTests: TestSuite = new TestSuite({ +const tokenTests = describe({ name: "token", suite: refreshTokenGrantTests, }); const clientService = new ClientService(); -test(tokenTests, "not implemented for AccessTokenService", async () => { +it(tokenTests, "not implemented for AccessTokenService", async () => { const tokenService: AccessTokenService = new AccessTokenService(); const getRefreshToken = spy(tokenService, "getRefreshToken"); const refreshTokenGrant = new RefreshTokenGrant({ @@ -64,7 +61,7 @@ test(tokenTests, "not implemented for AccessTokenService", async () => { "getRefreshToken not implemented", ); assertStrictEquals(getRefreshToken.calls.length, 1); - let call: SpyCall = getRefreshToken.calls[0]; + let call = getRefreshToken.calls[0]; assertStrictEquals(call.self, tokenService); assertEquals(call.args, ["example1"]); @@ -91,7 +88,7 @@ const refreshTokenGrant = new RefreshTokenGrant({ }, }); -test(tokenTests, "refresh_token parameter required", async () => { +it(tokenTests, "refresh_token parameter required", async () => { let request = fakeTokenRequest(""); const result = refreshTokenGrant.token( request, @@ -115,7 +112,7 @@ test(tokenTests, "refresh_token parameter required", async () => { const user: User = { username: "kyle" }; const scope: Scope = new Scope("read"); -test(tokenTests, "invalid refresh_token", async () => { +it(tokenTests, "invalid refresh_token", async () => { const getRefreshToken: Stub = stub( tokenService, "getRefreshToken", @@ -134,7 +131,7 @@ test(tokenTests, "invalid refresh_token", async () => { "invalid refresh_token", ); assertStrictEquals(getRefreshToken.calls.length, 1); - let call: SpyCall = getRefreshToken.calls[0]; + let call = getRefreshToken.calls[0]; assertStrictEquals(call.self, tokenService); assertEquals(call.args, ["example1"]); @@ -153,7 +150,7 @@ test(tokenTests, "invalid refresh_token", async () => { } }); -test(tokenTests, "expired refresh_token", async () => { +it(tokenTests, "expired refresh_token", async () => { const getRefreshToken: Stub = stub( tokenService, "getRefreshToken", @@ -189,7 +186,7 @@ test(tokenTests, "expired refresh_token", async () => { } }); -test(tokenTests, "refresh_token was issued to another client", async () => { +it(tokenTests, "refresh_token was issued to another client", async () => { const getRefreshToken: Stub = stub( tokenService, "getRefreshToken", @@ -215,7 +212,7 @@ test(tokenTests, "refresh_token was issued to another client", async () => { "refresh_token was issued to another client", ); assertStrictEquals(getRefreshToken.calls.length, 1); - let call: SpyCall = getRefreshToken.calls[0]; + let call = getRefreshToken.calls[0]; assertStrictEquals(call.self, tokenService); assertEquals(call.args, ["example1"]); @@ -234,7 +231,7 @@ test(tokenTests, "refresh_token was issued to another client", async () => { } }); -test(tokenTests, "returns new token and revokes old", async () => { +it(tokenTests, "returns new token and revokes old", async () => { const getRefreshToken = stub( tokenService, "getRefreshToken", @@ -276,9 +273,9 @@ test(tokenTests, "returns new token and revokes old", async () => { const token = await result; assertStrictEquals(getRefreshToken.calls.length, 1); - let call: SpyCall = getRefreshToken.calls[0]; - assertStrictEquals(call.self, tokenService); - assertEquals(call.args, ["example"]); + const getCall = getRefreshToken.calls[0]; + assertStrictEquals(getCall.self, tokenService); + assertEquals(getCall.args, ["example"]); assertClientUserScopeCall( generateToken, @@ -291,9 +288,9 @@ test(tokenTests, "returns new token and revokes old", async () => { assertSpyCalls(generateToken, 1); assertStrictEquals(revoke.calls.length, 1); - call = revoke.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], { + const revokeCall = revoke.calls[0]; + assertStrictEquals(revokeCall.args.length, 1); + assertToken(revokeCall.args[0], { accessToken: "fake", refreshToken: "example", client, @@ -311,9 +308,9 @@ test(tokenTests, "returns new token and revokes old", async () => { scope, }; assertStrictEquals(save.calls.length, 1); - call = save.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], expectedToken); + const saveCall = save.calls[0]; + assertStrictEquals(saveCall.args.length, 1); + assertToken(saveCall.args[0], expectedToken); assertToken(token, expectedToken); } finally { getRefreshToken.restore(); @@ -323,7 +320,7 @@ test(tokenTests, "returns new token and revokes old", async () => { } }); -test( +it( tokenTests, "returns new token with same refresh token and revokes old", async () => { @@ -364,9 +361,9 @@ test( const token = await result; assertStrictEquals(getRefreshToken.calls.length, 1); - let call: SpyCall = getRefreshToken.calls[0]; - assertStrictEquals(call.self, tokenService); - assertEquals(call.args, ["example"]); + const getCall = getRefreshToken.calls[0]; + assertStrictEquals(getCall.self, tokenService); + assertEquals(getCall.args, ["example"]); assertClientUserScopeCall( generateToken, @@ -379,9 +376,9 @@ test( assertSpyCalls(generateToken, 1); assertStrictEquals(revoke.calls.length, 1); - call = revoke.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], { + const revokeCall = revoke.calls[0]; + assertStrictEquals(revokeCall.args.length, 1); + assertToken(revokeCall.args[0], { accessToken: "fake", refreshToken: "example", refreshTokenExpiresAt, @@ -400,9 +397,9 @@ test( scope, }; assertStrictEquals(save.calls.length, 1); - call = save.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], expectedToken); + const saveCall = save.calls[0]; + assertStrictEquals(saveCall.args.length, 1); + assertToken(saveCall.args[0], expectedToken); assertToken(token, expectedToken); } finally { getRefreshToken.restore(); @@ -413,7 +410,7 @@ test( }, ); -test( +it( tokenTests, "returns new token with same code and revokes old", async () => { @@ -456,9 +453,9 @@ test( const token = await result; assertStrictEquals(getRefreshToken.calls.length, 1); - let call: SpyCall = getRefreshToken.calls[0]; - assertStrictEquals(call.self, tokenService); - assertEquals(call.args, ["example"]); + const getCall = getRefreshToken.calls[0]; + assertStrictEquals(getCall.self, tokenService); + assertEquals(getCall.args, ["example"]); assertClientUserScopeCall( generateToken, @@ -471,9 +468,9 @@ test( assertSpyCalls(generateToken, 1); assertStrictEquals(revoke.calls.length, 1); - call = revoke.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], { + const revokeCall = revoke.calls[0]; + assertStrictEquals(revokeCall.args.length, 1); + assertToken(revokeCall.args[0], { accessToken: "fake", refreshToken: "example", client, @@ -493,9 +490,9 @@ test( code: "z", }; assertStrictEquals(save.calls.length, 1); - call = save.calls[0]; - assertStrictEquals(call.args.length, 1); - assertToken(call.args[0], expectedToken); + const saveCall = save.calls[0]; + assertStrictEquals(saveCall.args.length, 1); + assertToken(saveCall.args[0], expectedToken); assertToken(token, expectedToken); } finally { getRefreshToken.restore(); diff --git a/models/scope_test.ts b/models/scope_test.ts index 16ba32f..59378a6 100644 --- a/models/scope_test.ts +++ b/models/scope_test.ts @@ -3,12 +3,12 @@ import { assertEquals, assertStrictEquals, assertThrows, - test, - TestSuite, + describe, + it, } from "../test_deps.ts"; import { InvalidScopeError } from "../errors.ts"; -test("SCOPE", () => { +it("SCOPE", () => { assertStrictEquals(SCOPE.test(""), true); assertStrictEquals(SCOPE.test("a"), true); assertStrictEquals(SCOPE.test(" "), false); @@ -25,7 +25,7 @@ test("SCOPE", () => { assertStrictEquals(SCOPE.test("a b\\c a d"), false); }); -test("SCOPE_TOKEN", () => { +it("SCOPE_TOKEN", () => { assertEquals("".match(SCOPE_TOKEN), null); assertEquals("a".match(SCOPE_TOKEN), ["a"]); assertEquals("a b a c".match(SCOPE_TOKEN), ["a", "b", "a", "c"]); @@ -37,9 +37,9 @@ test("SCOPE_TOKEN", () => { ]); }); -const scopeTests: TestSuite = new TestSuite({ name: "Scope" }); +const scopeTests = describe("Scope"); -test(scopeTests, "constructor validation", () => { +it(scopeTests, "constructor validation", () => { new Scope("a"); assertThrows(() => new Scope(" "), InvalidScopeError, "invalid scope"); assertThrows(() => new Scope(" a"), InvalidScopeError, "invalid scope"); @@ -59,7 +59,7 @@ test(scopeTests, "constructor validation", () => { ); }); -test(scopeTests, "toString", () => { +it(scopeTests, "toString", () => { let scope = new Scope("a"); assertStrictEquals(scope.toString(), "a"); assertStrictEquals(scope.toString(), "a"); @@ -71,7 +71,7 @@ test(scopeTests, "toString", () => { assertStrictEquals(scope.toString(), "!#0A[]a~ !#1B[]b~ !#2C[]c~"); }); -test(scopeTests, "toJSON", () => { +it(scopeTests, "toJSON", () => { let scope = new Scope("a"); assertStrictEquals(scope.toJSON(), "a"); assertStrictEquals(scope.toJSON(), "a"); @@ -83,7 +83,7 @@ test(scopeTests, "toJSON", () => { assertStrictEquals(scope.toJSON(), "!#0A[]a~ !#1B[]b~ !#2C[]c~"); }); -test(scopeTests, "from", () => { +it(scopeTests, "from", () => { assertStrictEquals(Scope.from("a").toString(), "a"); let scope: Scope = new Scope("a"); assertStrictEquals(Scope.from(scope).toString(), "a"); @@ -106,7 +106,7 @@ test(scopeTests, "from", () => { assertStrictEquals(scope.toString(), "!#0A[]a~ !#1B[]b~ !#2C[]c~"); }); -test(scopeTests, "has", () => { +it(scopeTests, "has", () => { let scope = new Scope("a"); assertStrictEquals(scope.has("a"), true); assertStrictEquals(scope.has(new Scope("a")), true); @@ -155,7 +155,7 @@ test(scopeTests, "has", () => { ); }); -test(scopeTests, "add", () => { +it(scopeTests, "add", () => { const scope: Scope = new Scope(); assertStrictEquals(scope.add("a"), scope); assertStrictEquals(scope.toString(), "a"); @@ -167,7 +167,7 @@ test(scopeTests, "add", () => { assertStrictEquals(scope.toString(), "a b c d e f g"); }); -test(scopeTests, "remove", () => { +it(scopeTests, "remove", () => { const scope: Scope = new Scope("a b c d e f g"); assertStrictEquals(scope.remove("a"), scope); assertStrictEquals(scope.toString(), "b c d e f g"); @@ -179,7 +179,7 @@ test(scopeTests, "remove", () => { assertStrictEquals(scope.toString(), ""); }); -test(scopeTests, "equals", () => { +it(scopeTests, "equals", () => { let scope = new Scope("a"); assertStrictEquals(scope.equals("a"), true); assertStrictEquals(scope.equals(new Scope("a")), true); @@ -203,7 +203,7 @@ test(scopeTests, "equals", () => { assertStrictEquals(scope.equals(new Scope("a b c d")), false); }); -test(scopeTests, "clear", () => { +it(scopeTests, "clear", () => { const scope: Scope = new Scope("a b c"); scope.add("d"); assertStrictEquals(scope.toString(), "a b c d"); @@ -215,7 +215,7 @@ test(scopeTests, "clear", () => { assertStrictEquals(scope.toString(), ""); }); -test(scopeTests, "union", () => { +it(scopeTests, "union", () => { const scopes: [Scope, Scope] = [ new Scope("a b c e"), new Scope("b d e f"), @@ -226,7 +226,7 @@ test(scopeTests, "union", () => { assertStrictEquals(scopes[1].toString(), "b d e f"); }); -test(scopeTests, "intersection", () => { +it(scopeTests, "intersection", () => { const scopes: [Scope, Scope] = [ new Scope("a b c e"), new Scope("b d e f"), @@ -237,6 +237,6 @@ test(scopeTests, "intersection", () => { assertStrictEquals(scopes[1].toString(), "b d e f"); }); -test(scopeTests, "iterator", () => { +it(scopeTests, "iterator", () => { assertEquals([...(new Scope("a c b d"))].sort(), ["a", "b", "c", "d"]); }); diff --git a/pkce_test.ts b/pkce_test.ts index 60f736f..d31807f 100644 --- a/pkce_test.ts +++ b/pkce_test.ts @@ -1,11 +1,11 @@ import { decodeBase64url } from "./deps.ts"; import { challengeMethods, generateCodeVerifier } from "./pkce.ts"; -import { assertEquals, test, TestSuite } from "./test_deps.ts"; +import { assertEquals, describe, it } from "./test_deps.ts"; -const pkceTests: TestSuite = new TestSuite({ name: "PKCE" }); +const pkceTests = describe("PKCE"); // https://datatracker.ietf.org/doc/html/rfc7636#appendix-B -test(pkceTests, "S256 challenge method", async () => { +it(pkceTests, "S256 challenge method", async () => { const { S256 } = challengeMethods; assertEquals( await S256("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"), @@ -13,7 +13,7 @@ test(pkceTests, "S256 challenge method", async () => { ); }); -test(pkceTests, "generateCodeVerifier", () => { +it(pkceTests, "generateCodeVerifier", () => { const encoder = new TextEncoder(); const encoded = encoder.encode(generateCodeVerifier()); assertEquals(encoded.length, 43); diff --git a/resource_server.ts b/resource_server.ts index deda10b..685b629 100644 --- a/resource_server.ts +++ b/resource_server.ts @@ -126,7 +126,6 @@ export class ResourceServer< request: Request, getAccessToken: ( request: Request, - requireRefresh?: boolean, ) => Promise, ): Promise> { let { token, accessToken } = request; @@ -135,15 +134,6 @@ export class ResourceServer< request.accessToken = null; accessToken = await getAccessToken(request); request.accessToken = accessToken; - if (accessToken) { - try { - token = await this.getToken(accessToken); - } catch (error) { - if (error.code !== "access_denied") throw error; - accessToken = await getAccessToken(request, true); - request.accessToken = accessToken; - } - } if (!accessToken) { accessToken = await this.getAccessToken(request); request.accessToken = accessToken; @@ -166,7 +156,6 @@ export class ResourceServer< next: () => Promise, getAccessToken: ( request: Request, - requireRefresh?: boolean, ) => Promise, acceptedScope?: Scope, ): Promise { @@ -284,5 +273,6 @@ export { UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, + UnsupportedTokenTypeError, } from "./errors.ts"; -export type { OAuth2ErrorInit } from "./errors.ts"; +export type { OAuth2ErrorOptions } from "./errors.ts"; diff --git a/resource_server_test.ts b/resource_server_test.ts index ee46031..9141963 100644 --- a/resource_server_test.ts +++ b/resource_server_test.ts @@ -17,12 +17,12 @@ import { assertSpyCall, assertSpyCalls, delay, + describe, + it, Spy, spy, Stub, stub, - test, - TestSuite, } from "./test_deps.ts"; import * as resourceServerModule from "./authorization_server.ts"; import { @@ -40,7 +40,7 @@ import { User, } from "./authorization_server.ts"; -test("verify exports", () => { +it("verify exports", () => { const moduleKeys = Object.keys(resourceServerModule).sort(); assertEquals(moduleKeys, [ "AbstractAccessTokenService", @@ -73,6 +73,7 @@ test("verify exports", () => { "UnauthorizedClientError", "UnsupportedGrantTypeError", "UnsupportedResponseTypeError", + "UnsupportedTokenTypeError", "VSCHAR", "authorizeParameters", "authorizeUrl", @@ -93,16 +94,11 @@ const server = new ResourceServer({ services: { tokenService }, }); -const serverTests = new TestSuite({ - name: "ResourceServer", -}); +const serverTests = describe("ResourceServer"); -const errorHandlerTests = new TestSuite({ - name: "errorHandler", - suite: serverTests, -}); +const errorHandlerTests = describe(serverTests, "errorHandler"); -test(errorHandlerTests, "OAuth2Error without optional properties", async () => { +it(errorHandlerTests, "OAuth2Error without optional properties", async () => { const request = fakeTokenRequest(); const response = fakeResponse(); const redirectSpy = spy(response, "redirect"); @@ -122,7 +118,7 @@ test(errorHandlerTests, "OAuth2Error without optional properties", async () => { assertEquals(redirectSpy.calls.length, 0); }); -test(errorHandlerTests, "OAuth2Error with optional properties", async () => { +it(errorHandlerTests, "OAuth2Error with optional properties", async () => { const request = fakeTokenRequest(); const response = fakeResponse(); const redirectSpy = spy(response, "redirect"); @@ -149,7 +145,7 @@ test(errorHandlerTests, "OAuth2Error with optional properties", async () => { assertEquals(redirectSpy.calls.length, 0); }); -test(errorHandlerTests, "OAuth2Error with 401 status", async () => { +it(errorHandlerTests, "OAuth2Error with 401 status", async () => { const request = fakeTokenRequest(); const response = fakeResponse(); const redirectSpy = spy(response, "redirect"); @@ -172,7 +168,7 @@ test(errorHandlerTests, "OAuth2Error with 401 status", async () => { assertEquals(redirectSpy.calls.length, 0); }); -test(errorHandlerTests, "Error", async () => { +it(errorHandlerTests, "Error", async () => { const request = fakeTokenRequest(); const response = fakeResponse(); const redirectSpy = spy(response, "redirect"); @@ -193,19 +189,16 @@ test(errorHandlerTests, "Error", async () => { assertEquals(redirectSpy.calls.length, 0); }); -const getAccessTokenTests = new TestSuite({ - name: "getAccessToken", - suite: serverTests, -}); +const getAccessTokenTests = describe(serverTests, "getAccessToken"); -test(getAccessTokenTests, "GET request with no access token", async () => { +it(getAccessTokenTests, "GET request with no access token", async () => { const request = fakeResourceRequest(""); const result = server.getAccessToken(request); assertEquals(Promise.resolve(result), result); assertEquals(await result, null); }); -test( +it( getAccessTokenTests, "GET request with access token in authorization header", async () => { @@ -216,14 +209,14 @@ test( }, ); -test(getAccessTokenTests, "POST request with no access token", async () => { +it(getAccessTokenTests, "POST request with no access token", async () => { const request = fakeResourceRequest(""); const result = server.getAccessToken(request); assertEquals(Promise.resolve(result), result); assertEquals(await result, null); }); -test( +it( getAccessTokenTests, "POST request with access token in authorization header", async () => { @@ -234,7 +227,7 @@ test( }, ); -test( +it( getAccessTokenTests, "POST request with access token in request body", async () => { @@ -247,7 +240,7 @@ test( }, ); -test( +it( getAccessTokenTests, "POST request with access token in authorization header and body", async () => { @@ -260,12 +253,9 @@ test( }, ); -const getTokenTests = new TestSuite({ - name: "getToken", - suite: serverTests, -}); +const getTokenTests = describe(serverTests, "getToken"); -test(getTokenTests, "token service required", async () => { +it(getTokenTests, "token service required", async () => { const { services } = server; try { server.services = {}; @@ -279,7 +269,7 @@ test(getTokenTests, "token service required", async () => { } }); -test(getTokenTests, "invalid access_token", async () => { +it(getTokenTests, "invalid access_token", async () => { const getToken = stub( tokenService, "getToken", @@ -303,7 +293,7 @@ test(getTokenTests, "invalid access_token", async () => { } }); -test(getTokenTests, "expired access_token", async () => { +it(getTokenTests, "expired access_token", async () => { const getToken = stub( tokenService, "getToken", @@ -334,16 +324,13 @@ test(getTokenTests, "expired access_token", async () => { } }); -const getTokenForRequestTests = new TestSuite({ - name: "getTokenForRequest", - suite: serverTests, -}); +const getTokenForRequestTests = describe(serverTests, "getTokenForRequest"); -test( +it( getTokenForRequestTests, "authentication required from previous call", async () => { - const getCustomAccessToken = spy(); + const getCustomAccessToken = spy(() => Promise.resolve(null)); const getAccessToken = spy(server, "getAccessToken"); const getToken = spy(server, "getToken"); try { @@ -364,11 +351,11 @@ test( }, ); -test( +it( getTokenForRequestTests, "invalid access_token from previous call", async () => { - const getCustomAccessToken = spy(); + const getCustomAccessToken = spy(() => Promise.resolve(null)); const getAccessToken = spy(server, "getAccessToken"); const getToken = spy(server, "getToken"); try { @@ -390,11 +377,11 @@ test( }, ); -test( +it( getTokenForRequestTests, "returns cached token from previous call", async () => { - const getCustomAccessToken = spy(); + const getCustomAccessToken = spy(() => Promise.resolve(null)); const getAccessToken = spy(server, "getAccessToken"); const getToken = spy(server, "getToken"); try { @@ -421,11 +408,11 @@ test( }, ); -test( +it( getTokenForRequestTests, "authentication required", async () => { - const getCustomAccessToken = spy(); + const getCustomAccessToken = spy(() => Promise.resolve(null)); const getAccessToken = spy(server, "getAccessToken"); const getToken = spy(server, "getToken"); try { @@ -457,11 +444,11 @@ test( }, ); -test( +it( getTokenForRequestTests, "invalid access token", async () => { - const getCustomAccessToken = spy(); + const getCustomAccessToken = spy(() => Promise.resolve(null)); const getAccessToken = spy(server, "getAccessToken"); const getToken = stub( server, @@ -501,11 +488,11 @@ test( }, ); -test( +it( getTokenForRequestTests, "returns token from authentication header", async () => { - const getCustomAccessToken = spy(); + const getCustomAccessToken = spy(() => Promise.resolve(null)); const getAccessToken = spy(server, "getAccessToken"); const expectedToken = { accessToken: "123", @@ -550,7 +537,7 @@ test( }, ); -test( +it( getTokenForRequestTests, "error from custom getAccessToken", async () => { @@ -583,7 +570,7 @@ test( }, ); -test( +it( getTokenForRequestTests, "non access denied error for token from custom getAccessToken", async () => { @@ -623,7 +610,7 @@ test( }, ); -test( +it( getTokenForRequestTests, "returns token from custom getAccessToken", async () => { @@ -668,123 +655,6 @@ test( }, ); -test( - getTokenForRequestTests, - "error for token from refreshed custom getAccessToken", - async () => { - const accessTokens = ["123", "456"]; - let accessTokenIndex = 0; - const getCustomAccessToken = spy(() => - Promise.resolve(accessTokens[accessTokenIndex++]) - ); - const getAccessToken = spy(server, "getAccessToken"); - let tokenCalls = 0; - const getToken = stub( - server, - "getToken", - () => - Promise.reject( - new AccessDeniedError(`invalid session ${++tokenCalls}`), - ), - ); - try { - const request = fakeResourceRequest(""); - await assertRejects( - () => server.getTokenForRequest(request, getCustomAccessToken), - AccessDeniedError, - "invalid session 2", - ); - assertSpyCall(getCustomAccessToken, 0, { - self: undefined, - args: [request], - }); - assertSpyCall(getCustomAccessToken, 1, { - self: undefined, - args: [request, true], - }); - assertSpyCalls(getCustomAccessToken, 2); - assertSpyCalls(getAccessToken, 0); - assertSpyCall(getToken, 0, { - self: server, - args: ["123"], - }); - assertSpyCall(getToken, 1, { - self: server, - args: ["456"], - }); - assertSpyCalls(getToken, 2); - - assertEquals(request.accessToken, "456"); - assertToken(request.token, null); - assertEquals(request.token, null); - } finally { - getAccessToken.restore(); - getToken.restore(); - } - }, -); - -test( - getTokenForRequestTests, - "returns token from refreshed custom getAccessToken", - async () => { - const accessTokens = ["123", "456"]; - let accessTokenIndex = 0; - const getCustomAccessToken = spy(() => - Promise.resolve(accessTokens[accessTokenIndex++]) - ); - const getAccessToken = spy(server, "getAccessToken"); - const expectedToken = { - accessToken: "456", - accessTokenExpiresAt: new Date(Date.now() - 60000), - client, - user, - scope, - }; - let tokenCalls = 0; - const getToken = stub( - server, - "getToken", - () => - tokenCalls++ === 0 - ? Promise.reject(new AccessDeniedError("invalid session")) - : Promise.resolve(expectedToken), - ); - try { - const request = fakeResourceRequest(""); - assertToken( - await server.getTokenForRequest(request, getCustomAccessToken), - expectedToken, - ); - assertSpyCall(getCustomAccessToken, 0, { - self: undefined, - args: [request], - }); - assertSpyCall(getCustomAccessToken, 1, { - self: undefined, - args: [request, true], - }); - assertSpyCalls(getCustomAccessToken, 2); - assertSpyCalls(getAccessToken, 0); - assertSpyCall(getToken, 0, { - self: server, - args: ["123"], - }); - assertSpyCall(getToken, 1, { - self: server, - args: ["456"], - }); - assertSpyCalls(getToken, 2); - - assertEquals(request.accessToken, "456"); - assertToken(request.token, expectedToken); - } finally { - getAccessToken.restore(); - getToken.restore(); - } - }, -); - interface AuthenticateTestContext { success: Spy; error: Spy; @@ -792,26 +662,26 @@ interface AuthenticateTestContext { authenticateError: Stub>; } -const authenticateTests = new TestSuite({ +const authenticateTests = describe({ name: "authenticate", suite: serverTests, - beforeEach(context: AuthenticateTestContext) { - context.success = spy(); - context.error = spy(); - context.authenticateSuccess = stub( + beforeEach() { + this.success = spy(); + this.error = spy(); + this.authenticateSuccess = stub( server, "authenticateSuccess", - () => delay(0).then(context.success), + () => delay(0).then(this.success), ); - context.authenticateError = stub( + this.authenticateError = stub( server, "authenticateError", - () => delay(0).then(context.error), + () => delay(0).then(this.error), ); }, - afterEach({ authenticateSuccess, authenticateError }) { - authenticateSuccess.restore(); - authenticateError.restore(); + afterEach() { + this.authenticateSuccess.restore(); + this.authenticateError.restore(); }, }); @@ -835,7 +705,7 @@ async function authenticateTestError< msg?: string, ) { const redirect = spy(response, "redirect"); - const next = spy(); + const next = spy(() => Promise.resolve()); await server.authenticate( request, response, @@ -847,7 +717,8 @@ async function authenticateTestError< assertSpyCalls(authenticateSuccess, 0); assertSpyCalls(success, 0); - const call = assertSpyCall(authenticateError, 0, { self: server }); + assertSpyCall(authenticateError, 0, { self: server }); + const call = authenticateError.calls[0]; assertEquals(call.args.length, 3); assertEquals(call.args.slice(0, 2), [request, response]); assertIsError(call.args[2], ErrorClass, msgIncludes, msg); @@ -865,10 +736,10 @@ async function authenticateTestError< assertSpyCalls(next, 0); } -test( +it( authenticateTests, "error getting token for request", - async (context) => { + async function () { const getTokenForRequest = stub( server, "getTokenForRequest", @@ -878,9 +749,9 @@ test( try { const request = fakeResourceRequest("123"); const response = fakeResponse(); - const getAccessToken = spy(); + const getAccessToken = spy(() => Promise.resolve(null)); await authenticateTestError( - context, + this, request, response, getAccessToken, @@ -902,7 +773,7 @@ test( }, ); -test(authenticateTests, "insufficient scope", async (context) => { +it(authenticateTests, "insufficient scope", async function () { const expectedToken = { accessToken: "123", client, @@ -918,10 +789,10 @@ test(authenticateTests, "insufficient scope", async (context) => { try { const request = fakeResourceRequest("123"); const response = fakeResponse(); - const getAccessToken = spy(); + const getAccessToken = spy(() => Promise.resolve(null)); const acceptedScope = new Scope("read write delete"); await authenticateTestError( - context, + this, request, response, getAccessToken, @@ -958,8 +829,8 @@ async function authenticateTest< () => Promise.resolve(expectedToken), ); const redirect = spy(response, "redirect"); - const next = spy(); - const getAccessToken = spy(); + const next = spy(() => Promise.resolve()); + const getAccessToken = spy(() => Promise.resolve(null)); try { await server.authenticate( @@ -1002,7 +873,7 @@ async function authenticateTest< assertSpyCalls(next, 0); } -test(authenticateTests, "without scope", async (context) => { +it(authenticateTests, "without scope", async function () { const request = fakeResourceRequest("123"); const response = fakeResponse(); const expectedToken = { @@ -1012,7 +883,7 @@ test(authenticateTests, "without scope", async (context) => { scope, }; await authenticateTest( - context, + this, request, response, undefined, @@ -1020,7 +891,7 @@ test(authenticateTests, "without scope", async (context) => { ); }); -test(authenticateTests, "with scope", async (context) => { +it(authenticateTests, "with scope", async function () { const request = fakeResourceRequest("123"); const response = fakeResponse(); const expectedToken = { @@ -1030,7 +901,7 @@ test(authenticateTests, "with scope", async (context) => { scope, }; await authenticateTest( - context, + this, request, response, scope, @@ -1038,12 +909,9 @@ test(authenticateTests, "with scope", async (context) => { ); }); -const authenticateResponseTests = new TestSuite({ - name: "authenticateResponse", - suite: serverTests, -}); +const authenticateResponseTests = describe(serverTests, "authenticateResponse"); -test(authenticateResponseTests, "without accepted scope", async () => { +it(authenticateResponseTests, "without accepted scope", async () => { const request = fakeResourceRequest("123"); request.token = { accessToken: "123", @@ -1063,7 +931,7 @@ test(authenticateResponseTests, "without accepted scope", async () => { assertSpyCalls(redirect, 0); }); -test(authenticateResponseTests, "with accepted scope", async () => { +it(authenticateResponseTests, "with accepted scope", async () => { const request = fakeResourceRequest("123"); request.token = { accessToken: "123", @@ -1084,7 +952,7 @@ test(authenticateResponseTests, "with accepted scope", async () => { assertSpyCalls(redirect, 0); }); -test(authenticateResponseTests, "without scope", async () => { +it(authenticateResponseTests, "without scope", async () => { const request = fakeResourceRequest("123"); request.token = { accessToken: "123", @@ -1104,7 +972,7 @@ test(authenticateResponseTests, "without scope", async () => { assertSpyCalls(redirect, 0); }); -test(serverTests, "authenticateSuccess", async () => { +it(serverTests, "authenticateSuccess", async () => { const authenticateResponseAwait = spy(); const authenticateResponse = stub( server, @@ -1148,7 +1016,7 @@ test(serverTests, "authenticateSuccess", async () => { } }); -test(serverTests, "authenticateError", async () => { +it(serverTests, "authenticateError", async () => { const authenticateResponseAwait = spy(); const authenticateResponse = stub( server, diff --git a/services/authorization_code_test.ts b/services/authorization_code_test.ts index 11673f8..905f55b 100644 --- a/services/authorization_code_test.ts +++ b/services/authorization_code_test.ts @@ -2,9 +2,9 @@ import { assert, assertEquals, assertStrictEquals, + describe, FakeTime, - test, - TestSuite, + it, v4, } from "../test_deps.ts"; import { @@ -16,11 +16,9 @@ import { const authorizationCodeService = new AuthorizationCodeService(); -const authorizationCodeServiceTests: TestSuite = new TestSuite({ - name: "AuthorizationCodeService", -}); +const authorizationCodeServiceTests = describe("AuthorizationCodeService"); -test(authorizationCodeServiceTests, "generateAuthorizationCode", async () => { +it(authorizationCodeServiceTests, "generateAuthorizationCode", async () => { const result = authorizationCodeService.generateCode( client, user, @@ -35,7 +33,7 @@ test(authorizationCodeServiceTests, "generateAuthorizationCode", async () => { ); }); -test(authorizationCodeServiceTests, "accessTokenExpiresAt", async () => { +it(authorizationCodeServiceTests, "accessTokenExpiresAt", async () => { const time: FakeTime = new FakeTime(); try { const fiveMinutes: number = 5 * 60 * 1000; diff --git a/services/client_test.ts b/services/client_test.ts index 20d4334..030e1df 100644 --- a/services/client_test.ts +++ b/services/client_test.ts @@ -1,14 +1,12 @@ -import { assertRejects, test, TestSuite } from "../test_deps.ts"; +import { assertRejects, describe, it } from "../test_deps.ts"; import { ServerError } from "../errors.ts"; import { client, ClientService } from "./test_services.ts"; const clientService = new ClientService(); -const clientServiceTests: TestSuite = new TestSuite({ - name: "ClientService", -}); +const clientServiceTests = describe("ClientService"); -test(clientServiceTests, "getUser", async () => { +it(clientServiceTests, "getUser", async () => { await assertRejects( () => clientService.getUser(client), ServerError, diff --git a/services/test_services.ts b/services/test_services.ts index 2aca6f2..744dc5f 100644 --- a/services/test_services.ts +++ b/services/test_services.ts @@ -51,7 +51,7 @@ export class AccessTokenService } /** Revokes a token. */ - async revoke(_token: Token): Promise { + async revoke(_token: string | Token): Promise { return await Promise.resolve(true); } @@ -103,7 +103,7 @@ export class RefreshTokenService } /** Revokes a token. */ - async revoke(_token: Token): Promise { + async revoke(_token: string | Token): Promise { return await Promise.resolve(true); } diff --git a/services/token.ts b/services/token.ts index ea8b18f..52dc9bb 100644 --- a/services/token.ts +++ b/services/token.ts @@ -52,9 +52,10 @@ export interface TokenServiceInterface< ): Promise | undefined>; /** Saves a token. */ save(token: Token): Promise>; - /** Revokes a token. Resolves true if a token was revoked. */ + /** Revokes a token. Resolves true if a token was revoked or invalid. */ revoke(token: Token): Promise; - /** Revokes all tokens for an authorization code. Resolves true if a token was revoked. */ + revoke(token: string, hint?: string | null): Promise; + /** Revokes all tokens for an authorization code. Resolves true if a authorization code was revoked or invalid. */ revokeCode(code: string): Promise; } @@ -140,10 +141,11 @@ export abstract class AbstractAccessTokenService< token: AccessToken, ): Promise>; - /** Revokes a token. Resolves true if a token was revoked. */ + /** Revokes a token. Resolves true if a token was revoked or invalid. */ abstract revoke(token: AccessToken): Promise; + abstract revoke(token: string, hint?: string | null): Promise; - /** Revokes all tokens for an authorization code. Resolves true if a token was revoked. */ + /** Revokes all tokens for an authorization code. Resolves true if a authorization code was revoked or invalid. */ abstract revokeCode(code: string): Promise; } @@ -193,6 +195,7 @@ export abstract class AbstractRefreshTokenService< token: Token, ): Promise>; - /** Revokes a token. Resolves true if a token was revoked. */ - abstract revoke(token: Token | string): Promise; + /** Revokes a token. Resolves true if a token was revoked or invalid. */ + abstract revoke(token: Token): Promise; + abstract revoke(token: string, hint?: string | null): Promise; } diff --git a/services/token_test.ts b/services/token_test.ts index dfa0d5e..e81d7c7 100644 --- a/services/token_test.ts +++ b/services/token_test.ts @@ -4,9 +4,9 @@ import { assertEquals, assertRejects, assertStrictEquals, + describe, FakeTime, - test, - TestSuite, + it, v4, } from "../test_deps.ts"; import { ServerError } from "../errors.ts"; @@ -20,11 +20,9 @@ import { const accessTokenService = new AccessTokenService(); -const accessTokenServiceTests: TestSuite = new TestSuite({ - name: "AccessTokenService", -}); +const accessTokenServiceTests = describe("AccessTokenService"); -test(accessTokenServiceTests, "generateAccessToken", async () => { +it(accessTokenServiceTests, "generateAccessToken", async () => { const result = accessTokenService.generateAccessToken( client, user, @@ -39,7 +37,7 @@ test(accessTokenServiceTests, "generateAccessToken", async () => { ); }); -test( +it( accessTokenServiceTests, "generateRefreshToken not implemented", async () => { @@ -59,7 +57,7 @@ test( }, ); -test(accessTokenServiceTests, "accessTokenExpiresAt", async () => { +it(accessTokenServiceTests, "accessTokenExpiresAt", async () => { const time: FakeTime = new FakeTime(); try { const hour: number = 60 * 60 * 1000; @@ -80,7 +78,7 @@ test(accessTokenServiceTests, "accessTokenExpiresAt", async () => { } }); -test( +it( accessTokenServiceTests, "accessTokenExpiresAt with client.accessTokenLifetime", async () => { @@ -114,7 +112,7 @@ test( }, ); -test( +it( accessTokenServiceTests, "refreshTokenExpiresAt not implemented", async () => { @@ -134,7 +132,7 @@ test( }, ); -test(accessTokenServiceTests, "getRefreshToken not implemented", async () => { +it(accessTokenServiceTests, "getRefreshToken not implemented", async () => { const result = accessTokenService.getRefreshToken("fake"); assertStrictEquals(Promise.resolve(result), result); await assertRejects( @@ -149,7 +147,7 @@ test(accessTokenServiceTests, "getRefreshToken not implemented", async () => { ); }); -test( +it( accessTokenServiceTests, "acceptedScope defaults to always passing", async () => { @@ -164,11 +162,9 @@ test( const refreshTokenService = new RefreshTokenService(); -const refreshTokenServiceTests: TestSuite = new TestSuite({ - name: "RefreshTokenService", -}); +const refreshTokenServiceTests = describe("RefreshTokenService"); -test(refreshTokenServiceTests, "generateAccessToken", async () => { +it(refreshTokenServiceTests, "generateAccessToken", async () => { const result = refreshTokenService.generateAccessToken( client, user, @@ -183,7 +179,7 @@ test(refreshTokenServiceTests, "generateAccessToken", async () => { ); }); -test(refreshTokenServiceTests, "accessTokenExpiresAt", async () => { +it(refreshTokenServiceTests, "accessTokenExpiresAt", async () => { const time = new FakeTime(); try { const hour = 60 * 60 * 1000; @@ -205,7 +201,7 @@ test(refreshTokenServiceTests, "accessTokenExpiresAt", async () => { } }); -test( +it( refreshTokenServiceTests, "accessTokenExpiresAt with client.accessTokenLifetime", async () => { @@ -237,7 +233,7 @@ test( }, ); -test(refreshTokenServiceTests, "generateRefreshToken", async () => { +it(refreshTokenServiceTests, "generateRefreshToken", async () => { const result = refreshTokenService .generateRefreshToken(client, user, scope); assertStrictEquals(Promise.resolve(result), result); @@ -253,7 +249,7 @@ test(refreshTokenServiceTests, "generateRefreshToken", async () => { ); }); -test(refreshTokenServiceTests, "refreshTokenExpiresAt", async () => { +it(refreshTokenServiceTests, "refreshTokenExpiresAt", async () => { const time = new FakeTime(); try { const twoWeeks = 14 * 24 * 60 * 60 * 1000; @@ -275,7 +271,7 @@ test(refreshTokenServiceTests, "refreshTokenExpiresAt", async () => { } }); -test( +it( refreshTokenServiceTests, "refreshTokenExpiresAt with client.refreshTokenLifetime", async () => { diff --git a/services/user_test.ts b/services/user_test.ts index ac65575..2fdbfd8 100644 --- a/services/user_test.ts +++ b/services/user_test.ts @@ -3,8 +3,8 @@ import { assertNotEquals, assertRejects, assertStrictEquals, - test, - TestSuite, + describe, + it, } from "../test_deps.ts"; import { ServerError } from "../errors.ts"; import { UserService } from "./test_services.ts"; @@ -12,11 +12,9 @@ import { generateSalt, hashPassword } from "./user.ts"; const userService = new UserService(); -const userServiceTests: TestSuite = new TestSuite({ - name: "UserService", -}); +const userServiceTests = describe("UserService"); -test(userServiceTests, "getAuthenticated not implemented", async () => { +it(userServiceTests, "getAuthenticated not implemented", async () => { const result = userService.getAuthenticated( "Kyle", "hunter2", @@ -30,7 +28,7 @@ test(userServiceTests, "getAuthenticated not implemented", async () => { ); }); -test("generateSalt", () => { +it("generateSalt", () => { const salts = [ generateSalt(), generateSalt(), @@ -40,7 +38,7 @@ test("generateSalt", () => { assertEquals(salts[1].length, 32); }); -test("hashPassword", async () => { +it("hashPassword", async () => { const passwords = ["hunter1", "hunter2"]; const salts = [ "ba387b742a3e1917d084d067e3a65b63", diff --git a/test_deps.ts b/test_deps.ts index 1e03f05..0c658e0 100644 --- a/test_deps.ts +++ b/test_deps.ts @@ -8,22 +8,18 @@ export { assertRejects, assertStrictEquals, assertThrows, -} from "https://deno.land/std@0.120.0/testing/asserts.ts"; -export { delay } from "https://deno.land/std@0.120.0/async/delay.ts"; -export { v4 } from "https://deno.land/std@0.120.0/uuid/mod.ts"; +} from "https://deno.land/std@0.133.0/testing/asserts.ts"; +export { delay } from "https://deno.land/std@0.133.0/async/delay.ts"; +export { v4 } from "https://deno.land/std@0.133.0/uuid/mod.ts"; export { assertSpyCall, assertSpyCallAsync, assertSpyCalls, - FakeTime, spy, stub, -} from "https://deno.land/x/mock@0.12.2/mod.ts"; -export type { - Spy, - SpyCall, - Stub, -} from "https://deno.land/x/mock@0.12.2/mod.ts"; +} from "https://deno.land/std@0.133.0/testing/mock.ts"; +export type { Spy, Stub } from "https://deno.land/std@0.133.0/testing/mock.ts"; +export { FakeTime } from "https://deno.land/x/mock@0.15.0/time.ts"; -export { test, TestSuite } from "https://deno.land/x/test_suite@0.9.5/mod.ts"; +export { describe, it } from "https://deno.land/x/test_suite@0.16.0/mod.ts";