Skip to content

Commit 0789079

Browse files
committed
feat: more helpers and tests
1 parent 2221169 commit 0789079

15 files changed

+489
-31
lines changed

lib/utils/token/getClaim.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { JWTDecoded } from "@kinde/jwt-decoder";
2+
import { getDecodedToken } from "./getDecodedToken";
3+
4+
/**
5+
*
6+
* @param keyName key to get from the token
7+
* @returns { Promise<string | number | string[] | null> }
8+
*/
9+
export const getClaim = async <T = JWTDecoded, V = string | number | string[]>(
10+
keyName: keyof T,
11+
): Promise<{
12+
name: keyof T;
13+
value: V;
14+
} | null> => {
15+
const claims = await getDecodedToken<T>("accessToken");
16+
if (!claims) {
17+
return null;
18+
}
19+
return {
20+
name: keyName,
21+
value: claims[keyName] as V,
22+
};
23+
};

lib/utils/token/getClaims.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { JWTDecoded } from "@kinde/jwt-decoder";
2+
import { getDecodedToken } from "./getDecodedToken";
3+
4+
/**
5+
* get all claims from the token
6+
* @returns { Promise<T | null> }
7+
*/
8+
export const getClaims = async <T = JWTDecoded>(): Promise<T | null> => {
9+
return getDecodedToken<T>("accessToken");
10+
};
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { getClaim } from "./getClaim";
2+
3+
/**
4+
*
5+
* @param keyName key to get from the token
6+
* @returns { Promise<string | number | string[] | null> }
7+
**/
8+
export const getCurrentOrganization = async (): Promise<string | null> => {
9+
return (
10+
(await getClaim<{ org_code: string }, string>("org_code"))?.value || null
11+
);
12+
};

lib/utils/token/getDecodedToken.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,11 @@ export const getDecodedToken = async <
1515
tokenType: "accessToken" | "idToken" = "accessToken",
1616
): Promise<T | null> => {
1717
const activeStorage = getActiveStorage();
18-
if (!activeStorage) {
19-
throw new Error("Session manager is not initialized");
20-
}
2118

2219
const token = (await activeStorage.getSessionItem(
2320
tokenType === "accessToken" ? StorageKeys.accessToken : StorageKeys.idToken,
2421
)) as string;
25-
console.log("token", token);
22+
2623
if (!token) {
2724
return null;
2825
}

lib/utils/token/getFlag.test.ts

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { describe, expect, it, beforeEach } from "vitest";
2+
import { MemoryStorage, StorageKeys } from "../../sessionManager";
3+
import { setActiveStorage, getFlag } from ".";
4+
import { createMockAccessToken } from "./testUtils";
5+
6+
const storage = new MemoryStorage();
7+
8+
describe("getFlag", () => {
9+
beforeEach(() => {
10+
setActiveStorage(storage);
11+
});
12+
it("when no token", async () => {
13+
await storage.setSessionItem(StorageKeys.idToken, null);
14+
const idToken = await getFlag("test");
15+
16+
expect(idToken).toStrictEqual(null);
17+
});
18+
19+
it("boolean", async () => {
20+
await storage.setSessionItem(
21+
StorageKeys.accessToken,
22+
createMockAccessToken({
23+
feature_flags: {
24+
test: {
25+
v: true,
26+
t: "b",
27+
},
28+
},
29+
}),
30+
);
31+
const idToken = await getFlag<boolean>("test");
32+
33+
expect(idToken).toStrictEqual(true);
34+
});
35+
36+
it("string", async () => {
37+
await storage.setSessionItem(
38+
StorageKeys.accessToken,
39+
createMockAccessToken({
40+
feature_flags: {
41+
test: {
42+
v: "hello",
43+
t: "s",
44+
},
45+
},
46+
}),
47+
);
48+
const idToken = await getFlag<string>("test");
49+
50+
expect(idToken).toStrictEqual("hello");
51+
});
52+
53+
it("integer", async () => {
54+
await storage.setSessionItem(
55+
StorageKeys.accessToken,
56+
createMockAccessToken({
57+
feature_flags: {
58+
test: {
59+
v: 5,
60+
t: "i",
61+
},
62+
},
63+
}),
64+
);
65+
const idToken = await getFlag<number>("test");
66+
67+
expect(idToken).toStrictEqual(5);
68+
});
69+
70+
it("no existing flag", async () => {
71+
await storage.setSessionItem(
72+
StorageKeys.accessToken,
73+
createMockAccessToken({
74+
feature_flags: {
75+
test: {
76+
v: 5,
77+
t: "i",
78+
},
79+
},
80+
}),
81+
);
82+
const idToken = await getFlag<number>("noexist");
83+
84+
expect(idToken).toStrictEqual(null);
85+
});
86+
});

lib/utils/token/getFlag.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getClaim } from "./getClaim";
2+
3+
/**
4+
*
5+
* @param keyName key to get from the token
6+
* @returns { Promise<string | number | string[] | null> }
7+
*/
8+
export const getFlag = async <T = string | boolean | number>(
9+
name: string,
10+
): Promise<T | null> => {
11+
const flags = (
12+
await getClaim<
13+
{ feature_flags: string },
14+
Record<string, { t: "b" | "i" | "s"; v: T }>
15+
>("feature_flags")
16+
)?.value;
17+
18+
if (name && flags) {
19+
const value = flags[name];
20+
return value?.v || null;
21+
}
22+
return null;
23+
};

lib/utils/token/getPermission.test.ts

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { describe, expect, it, beforeEach } from "vitest";
2+
import { MemoryStorage, StorageKeys } from "../../sessionManager";
3+
import { setActiveStorage, getPermission } from ".";
4+
import { createMockAccessToken } from "./testUtils";
5+
6+
const storage = new MemoryStorage();
7+
8+
enum PermissionEnum {
9+
canEdit = "canEdit",
10+
}
11+
12+
describe("getPermission", () => {
13+
beforeEach(() => {
14+
setActiveStorage(storage);
15+
});
16+
it("when no token", async () => {
17+
await storage.setSessionItem(StorageKeys.idToken, null);
18+
const idToken = await getPermission("test");
19+
20+
expect(idToken).toStrictEqual({
21+
isGranted: false,
22+
orgCode: null,
23+
permissionKey: "test",
24+
});
25+
});
26+
27+
it("when no token with enum", async () => {
28+
await storage.setSessionItem(StorageKeys.idToken, null);
29+
const idToken = await getPermission<PermissionEnum>(PermissionEnum.canEdit);
30+
31+
expect(idToken).toStrictEqual({
32+
isGranted: false,
33+
orgCode: null,
34+
permissionKey: "canEdit",
35+
});
36+
});
37+
38+
it("with access", async () => {
39+
await storage.setSessionItem(
40+
StorageKeys.accessToken,
41+
createMockAccessToken({ permissions: ["canEdit"] }),
42+
);
43+
const idToken = await getPermission<PermissionEnum>(PermissionEnum.canEdit);
44+
45+
expect(idToken).toStrictEqual({
46+
isGranted: true,
47+
orgCode: "org_123456789",
48+
permissionKey: "canEdit",
49+
});
50+
});
51+
52+
it("with access different org", async () => {
53+
await storage.setSessionItem(
54+
StorageKeys.accessToken,
55+
createMockAccessToken({
56+
permissions: ["canEdit"],
57+
org_code: "org_123456799",
58+
}),
59+
);
60+
const idToken = await getPermission<PermissionEnum>(PermissionEnum.canEdit);
61+
62+
expect(idToken).toStrictEqual({
63+
isGranted: true,
64+
orgCode: "org_123456799",
65+
permissionKey: "canEdit",
66+
});
67+
});
68+
69+
it("no access, empty permission array", async () => {
70+
await storage.setSessionItem(
71+
StorageKeys.accessToken,
72+
createMockAccessToken({ permissions: null }),
73+
);
74+
const idToken = await getPermission<PermissionEnum>(PermissionEnum.canEdit);
75+
76+
expect(idToken).toStrictEqual({
77+
isGranted: false,
78+
orgCode: "org_123456789",
79+
permissionKey: "canEdit",
80+
});
81+
});
82+
});

lib/utils/token/getPermission.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { getDecodedToken } from ".";
2+
3+
export type PermissionAccess = {
4+
permissionKey: string;
5+
orgCode: string | null;
6+
isGranted: boolean;
7+
};
8+
9+
/**
10+
*
11+
* @param permissionKey gets the value of a permission
12+
* @returns { PermissionAccess }
13+
*/
14+
export const getPermission = async <T = string>(
15+
permissionKey: T,
16+
): Promise<PermissionAccess> => {
17+
const token = await getDecodedToken();
18+
19+
if (!token) {
20+
return {
21+
permissionKey: permissionKey as string,
22+
orgCode: null,
23+
isGranted: false,
24+
};
25+
}
26+
27+
const permissions = token.permissions || [];
28+
return {
29+
permissionKey: permissionKey as string,
30+
orgCode: token.org_code,
31+
isGranted: !!permissions.includes(permissionKey as string),
32+
};
33+
};
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, expect, it, beforeEach } from "vitest";
2+
import { MemoryStorage, StorageKeys } from "../../sessionManager";
3+
import { setActiveStorage } from ".";
4+
import { createMockAccessToken } from "./testUtils";
5+
import { getPermissions } from ".";
6+
7+
const storage = new MemoryStorage();
8+
9+
enum PermissionEnum {
10+
canEdit = "canEdit",
11+
}
12+
13+
describe("getPermissions", () => {
14+
beforeEach(() => {
15+
setActiveStorage(storage);
16+
});
17+
18+
it("when no token", async () => {
19+
await storage.setSessionItem(StorageKeys.idToken, null);
20+
const idToken = await getPermissions();
21+
22+
expect(idToken).toStrictEqual({
23+
orgCode: null,
24+
permissions: [],
25+
});
26+
});
27+
28+
it("with value", async () => {
29+
await storage.setSessionItem(
30+
StorageKeys.accessToken,
31+
createMockAccessToken({ permissions: ["canEdit"] }),
32+
);
33+
const idToken = await getPermissions();
34+
35+
expect(idToken).toStrictEqual({
36+
orgCode: "org_123456789",
37+
permissions: ["canEdit"],
38+
});
39+
});
40+
41+
it("with value and typed permissions", async () => {
42+
await storage.setSessionItem(
43+
StorageKeys.accessToken,
44+
createMockAccessToken({ permissions: ["canEdit"] }),
45+
);
46+
const idToken = await getPermissions<PermissionEnum>();
47+
48+
expect(idToken).toStrictEqual({
49+
orgCode: "org_123456789",
50+
permissions: [PermissionEnum.canEdit],
51+
});
52+
});
53+
54+
it("no permissions array", async () => {
55+
await storage.setSessionItem(
56+
StorageKeys.accessToken,
57+
createMockAccessToken({ permissions: null }),
58+
);
59+
const idToken = await getPermissions<PermissionEnum>();
60+
61+
expect(idToken).toStrictEqual({
62+
orgCode: "org_123456789",
63+
permissions: [],
64+
});
65+
});
66+
});

lib/utils/token/getPermissions.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { getDecodedToken } from ".";
2+
3+
export type Permissions<T> = { orgCode: string | null; permissions: T[] };
4+
/**
5+
* Get all permissions
6+
* @returns { Promise<Permissions> }
7+
*/
8+
export const getPermissions = async <T = string>(): Promise<Permissions<T>> => {
9+
const token = await getDecodedToken();
10+
11+
if (!token) {
12+
return {
13+
orgCode: null,
14+
permissions: [],
15+
};
16+
}
17+
18+
const permissions = token.permissions || [];
19+
return {
20+
orgCode: token.org_code,
21+
permissions: permissions as T[],
22+
};
23+
};

0 commit comments

Comments
 (0)