Skip to content

Commit

Permalink
Merge pull request #12 from kilterset/extract-apis
Browse files Browse the repository at this point in the history
Extract API implementations from Post Login API
  • Loading branch information
matt-wratt authored Apr 11, 2024
2 parents e45d984 + 042fc6f commit 5ee473b
Show file tree
Hide file tree
Showing 27 changed files with 1,333 additions and 318 deletions.
4 changes: 2 additions & 2 deletions examples/geo-filter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ test("Filter access based on continent code", async (t) => {
ok(action.access.denied, "Expected access to be denied");

strictEqual(
action.access.code,
action.access.denied.code,
"invalid_request",
"Unexpected denial code"
);

strictEqual(
action.access.reason,
action.access.denied.reason,
"Access from North America is not allowed.",
"Unexpected denial reason"
);
Expand Down
2 changes: 1 addition & 1 deletion examples/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions examples/redirect.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,26 @@ test("redirect and continue with signed data", async (t) => {
const { redirect } = action;

strict(
redirect.queryParams.theme,
redirect.target.queryParams.theme,
"spiffy",
"Unexpected value for `theme` query parameter"
);

strictEqual(
// You can also use redirect.url.href to get the full URL as a string
redirect.url.origin,
redirect.target.url.origin,
"https://example.com",
"Unexpected redirect URL origin"
);

strictEqual(
redirect.url.pathname,
redirect.target.url.pathname,
"/sandwich-preferences",
"Unexpected redirect URL path"
);

// Test the signed JWT data payload
const { session_token } = redirect.queryParams;
const { session_token } = redirect.target.queryParams;

const decoded = jwt.decodeJWTPayload(session_token);

Expand Down
73 changes: 73 additions & 0 deletions src/mock/api/access-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
interface AccessTokenMock {
build: <T>(api: T) => {
addScope: (name: string) => T;
removeScope: (name: string) => T;
setCustomClaim: (name: string, value: unknown) => T;
};
state: {
scopes: string[];
claims: Record<string, unknown>;
};
}

interface CredentialsExchangeAccessTokenMock {
build: <T>(api: T) => {
setCustomClaim: (name: string, value: unknown) => T;
};
state: {
claims: Record<string, unknown>;
};
}

export function accessTokenMock(flow: "CredentialsExchange"): CredentialsExchangeAccessTokenMock;
export function accessTokenMock(flow: string): AccessTokenMock;
export function accessTokenMock(flow: string) {
switch(flow) {
case "CredentialsExchange": {
const state = {
claims: {} as Record<string, unknown>,
};

const build = <T>(api: T) => ({
setCustomClaim: (name: string, value: unknown) => {
state.claims[name] = value;
return api;
},
})

return { build, state };
}

default: {
const state = {
scopes: [] as string[],
claims: {} as Record<string, unknown>,
};

const build = <T>(api: T) => ({
addScope: (name: string) => {
state.scopes = [
...new Set(state.scopes).add(name),
];

return api;
},

removeScope: (name: string) => {
state.scopes = state.scopes.filter(
(value) => value !== name
);

return api;
},

setCustomClaim: (name: string, value: unknown) => {
state.claims[name] = value;
return api;
},
})

return { build, state };
}
}
}
53 changes: 53 additions & 0 deletions src/mock/api/access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
interface AccessMock {
build: <API>(api: API) => {
deny: (reason: string) => API;
};
state: {
denied: false | { reason: string };
};
};

interface CredentialsExchangeAccessMock {
build: <API>(api: API) => {
deny: (code: string, reason: string) => API;
};
state: {
denied: false | { code: string; reason: string };
};
};

export function accessMock(flow: "CredentialsExchange"): CredentialsExchangeAccessMock;
export function accessMock(flow: string): AccessMock;
export function accessMock(flow: string) {
switch(flow) {
case "CredentialsExchange": {
const state = {
denied: false as false | { code: string; reason: string },
};

const build = <API>(api: API) => ({
deny: (code: string, reason: string) => {
state.denied = { code, reason };
return api;
},
});

return { build, state };
}

default: {
const state = {
denied: false as false | { reason: string },
};

const build = <API>(api: API) => ({
deny: (reason: string) => {
state.denied = { reason };
return api;
},
});

return { build, state };
}
}
}
65 changes: 65 additions & 0 deletions src/mock/api/authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Factor } from "../../types";

export interface FactorList {
allOptions: Factor[];
default: Factor | undefined;
}

export function authenticationMock(_flow: string, { userId }: { userId: string }) {
let numCallsToSetPrimaryUser = 0;

const state = {
primaryUserId: userId,
challenge: false as false | FactorList,
enrollment: false as false | FactorList,
newlyRecordedMethods: [] as string[],
};

const build = <T>(api: T) => ({
challengeWith: (factor: Factor, options?: { additionalFactors?: Factor[] }) => {
const additionalFactors = options?.additionalFactors ?? [];

state.challenge = {
allOptions: [factor, ...additionalFactors],
default: factor,
};
},
challengeWithAny(factors: Factor[]) {
state.challenge = {
allOptions: factors,
default: undefined,
};
},
enrollWith: (factor: Factor, options?: { additionalFactors?: Factor[] }) => {
const additionalFactors = options?.additionalFactors ?? [];

state.enrollment = {
allOptions: [factor, ...additionalFactors],
default: factor,
};
},
enrollWithAny(factors: Factor[]) {
state.enrollment = {
allOptions: factors,
default: undefined,
};
},
setPrimaryUser: (primaryUserId: string) => {
numCallsToSetPrimaryUser++;

if (numCallsToSetPrimaryUser > 1) {
throw new Error(
"`authentication.setPrimaryUser` can only be set once per transaction"
);
}

state.primaryUserId = primaryUserId;
},
recordMethod: (providerUrl: string) => {
state.newlyRecordedMethods.push(providerUrl);
return api;
},
});

return { state, build };
}
27 changes: 11 additions & 16 deletions src/mock/api/credentials-exchange.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Auth0 from "../../types";
import { cache as mockCache } from "./cache";
import { request as mockRequest } from "../request";
import { accessTokenMock } from "./access-token";
import { accessMock } from "./access";

export interface CredentialsExchangeOptions {
cache?: Record<string, string>;
}

export interface CredentialsExchangeState {
access: { denied: false } | { denied: true; code: string; reason: string };
access: { denied: false } | { denied: { code: string; reason: string } };
accessToken: {
claims: Record<string, unknown>;
};
Expand All @@ -18,28 +19,22 @@ export function credentialsExchange({
cache,
}: CredentialsExchangeOptions = {}) {
const apiCache = mockCache(cache);
const access = accessMock("CredentialsExchange");
const accessToken = accessTokenMock("CredentialsExchange")

const state: CredentialsExchangeState = {
access: { denied: false },
accessToken: {
claims: {},
},
access: access.state,
accessToken: accessToken.state,
cache: apiCache,
};

const api: Auth0.API.CredentialsExchange = {
access: {
deny: (code, reason) => {
state.access = { denied: true, code, reason };
return api;
},
get access() {
return access.build(api);
},

accessToken: {
setCustomClaim: (name, value) => {
state.accessToken.claims[name] = value;
return api;
},
get accessToken() {
return accessToken.build(api);
},

cache: apiCache,
Expand Down
14 changes: 14 additions & 0 deletions src/mock/api/id-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function idTokenMock(flow: string) {
const state = {
claims: {} as Record<string, unknown>,
};

const build = <T>(api: T) => ({
setCustomClaim: (name: string, value: unknown) => {
state.claims[name] = value;
return api;
},
});

return { state, build };
}
16 changes: 16 additions & 0 deletions src/mock/api/multifactor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MultifactorEnableOptions } from "../../types";

export function multifactorMock(flow: string) {
const state = {
enabled: false as false | { provider: string; options?: MultifactorEnableOptions },
};

const build = <T>(api: T) => ({
enable: (provider: string, options?: MultifactorEnableOptions) => {
state.enabled = { provider, options };
return api;
},
});

return { state, build };
}
Loading

0 comments on commit 5ee473b

Please sign in to comment.