@headless-lms/adapter-auth 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ import { AccessScope } from "@headless-lms/core/organizations";
2
+ import { AccessTokenAuthorization } from "better-auth/oauth2";
3
+ import { AccessGrant } from "@headless-lms/core/types";
4
+ //#region src/access-grant.d.ts
5
+ declare function accessTokenAuthorization(header: string | string[] | undefined): AccessTokenAuthorization | null;
6
+ declare function isCompactJws(token: string): boolean;
7
+ declare function grantFromClaims(claims: Record<string, unknown>): AccessGrant | null;
8
+ declare function needsOrgChoice(activeOrgId: string | null, memberOrgIds: () => Promise<readonly string[]>): Promise<boolean>;
9
+ declare function consentOrgId(activeOrgId: string | null, memberOrgIds: () => Promise<readonly string[]>): Promise<string | null>;
10
+ declare function orgIdFromMetadata(metadata: unknown): string | null;
11
+ declare function scopesToPermissions(scopes: string[]): Record<string, string[]>;
12
+ declare function permissionsToScopes(permissions: unknown): AccessScope[];
13
+ //#endregion
14
+ export { accessTokenAuthorization, consentOrgId, grantFromClaims, isCompactJws, needsOrgChoice, orgIdFromMetadata, permissionsToScopes, scopesToPermissions };
15
+ //# sourceMappingURL=access-grant.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { ORG_CLAIM } from "@headless-lms/core/identity";
2
+ import { ACCESS_SCOPES, isAccessScope } from "@headless-lms/core/organizations";
3
+ import { parseAccessTokenAuthorization } from "better-auth/oauth2";
4
+ //#region src/access-grant.ts
5
+ function accessTokenAuthorization(header) {
6
+ if (typeof header !== "string") return null;
7
+ const authorization = parseAccessTokenAuthorization(header);
8
+ if (!authorization?.token || authorization.scheme === "Unknown") return null;
9
+ return authorization;
10
+ }
11
+ function isCompactJws(token) {
12
+ const parts = token.split(".");
13
+ return parts.length === 3 && parts.every((part) => part.length > 0);
14
+ }
15
+ function grantFromClaims(claims) {
16
+ const userId = claims.sub;
17
+ const clientId = claims.client_id ?? claims.azp;
18
+ const orgId = claims[ORG_CLAIM];
19
+ if (typeof userId !== "string" || typeof clientId !== "string" || typeof orgId !== "string") return null;
20
+ return {
21
+ userId,
22
+ clientId,
23
+ orgId,
24
+ scopes: typeof claims.scope === "string" ? claims.scope.split(" ").filter(isAccessScope) : []
25
+ };
26
+ }
27
+ async function needsOrgChoice(activeOrgId, memberOrgIds) {
28
+ if (activeOrgId === null) return true;
29
+ return !(await memberOrgIds()).includes(activeOrgId);
30
+ }
31
+ async function consentOrgId(activeOrgId, memberOrgIds) {
32
+ return activeOrgId !== null && !await needsOrgChoice(activeOrgId, memberOrgIds) ? activeOrgId : null;
33
+ }
34
+ function orgIdFromMetadata(metadata) {
35
+ let value = metadata;
36
+ if (typeof value === "string") try {
37
+ value = JSON.parse(value);
38
+ } catch {
39
+ return null;
40
+ }
41
+ const orgId = value?.organizationId;
42
+ return typeof orgId === "string" ? orgId : null;
43
+ }
44
+ function scopesToPermissions(scopes) {
45
+ const permissions = {};
46
+ for (const scope of new Set(scopes.filter(isAccessScope))) {
47
+ const separator = scope.indexOf(":");
48
+ const resource = scope.slice(0, separator);
49
+ permissions[resource] = [...permissions[resource] ?? [], scope.slice(separator + 1)];
50
+ }
51
+ return permissions;
52
+ }
53
+ function permissionsToScopes(permissions) {
54
+ if (permissions === null || typeof permissions !== "object") return [];
55
+ const granted = /* @__PURE__ */ new Set();
56
+ for (const [resource, actions] of Object.entries(permissions)) if (Array.isArray(actions)) for (const action of actions) granted.add(`${resource}:${String(action)}`);
57
+ return ACCESS_SCOPES.filter((scope) => granted.has(scope));
58
+ }
59
+ //#endregion
60
+ export { accessTokenAuthorization, consentOrgId, grantFromClaims, isCompactJws, needsOrgChoice, orgIdFromMetadata, permissionsToScopes, scopesToPermissions };
61
+
62
+ //# sourceMappingURL=access-grant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-grant.js","names":[],"sources":["../src/access-grant.ts"],"sourcesContent":["import type { AccessGrant } from \"@headless-lms/core/types\";\nimport { ORG_CLAIM } from \"@headless-lms/core/identity\";\nimport { ACCESS_SCOPES, type AccessScope, isAccessScope } from \"@headless-lms/core/organizations\";\nimport { type AccessTokenAuthorization, parseAccessTokenAuthorization } from \"better-auth/oauth2\";\n\nexport function accessTokenAuthorization(\n header: string | string[] | undefined,\n): AccessTokenAuthorization | null {\n if (typeof header !== \"string\") {\n return null;\n }\n const authorization = parseAccessTokenAuthorization(header);\n if (!authorization?.token || authorization.scheme === \"Unknown\") {\n return null;\n }\n return authorization;\n}\n\nexport function isCompactJws(token: string): boolean {\n const parts = token.split(\".\");\n return parts.length === 3 && parts.every((part) => part.length > 0);\n}\n\nexport function grantFromClaims(claims: Record<string, unknown>): AccessGrant | null {\n const userId = claims.sub;\n const clientId = claims.client_id ?? claims.azp;\n const orgId = claims[ORG_CLAIM];\n if (typeof userId !== \"string\" || typeof clientId !== \"string\" || typeof orgId !== \"string\") {\n return null;\n }\n const scopes =\n typeof claims.scope === \"string\" ? claims.scope.split(\" \").filter(isAccessScope) : [];\n return { userId, clientId, orgId, scopes };\n}\n\nexport async function needsOrgChoice(\n activeOrgId: string | null,\n memberOrgIds: () => Promise<readonly string[]>,\n): Promise<boolean> {\n if (activeOrgId === null) {\n return true;\n }\n return !(await memberOrgIds()).includes(activeOrgId);\n}\n\nexport async function consentOrgId(\n activeOrgId: string | null,\n memberOrgIds: () => Promise<readonly string[]>,\n): Promise<string | null> {\n return activeOrgId !== null && !(await needsOrgChoice(activeOrgId, memberOrgIds))\n ? activeOrgId\n : null;\n}\n\nexport function orgIdFromMetadata(metadata: unknown): string | null {\n let value = metadata;\n if (typeof value === \"string\") {\n try {\n value = JSON.parse(value);\n } catch {\n return null;\n }\n }\n const orgId = (value as { organizationId?: unknown } | null)?.organizationId;\n return typeof orgId === \"string\" ? orgId : null;\n}\n\nexport function scopesToPermissions(scopes: string[]): Record<string, string[]> {\n const permissions: Record<string, string[]> = {};\n for (const scope of new Set(scopes.filter(isAccessScope))) {\n const separator = scope.indexOf(\":\");\n const resource = scope.slice(0, separator);\n permissions[resource] = [...(permissions[resource] ?? []), scope.slice(separator + 1)];\n }\n return permissions;\n}\n\nexport function permissionsToScopes(permissions: unknown): AccessScope[] {\n if (permissions === null || typeof permissions !== \"object\") {\n return [];\n }\n const granted = new Set<string>();\n for (const [resource, actions] of Object.entries(permissions)) {\n if (Array.isArray(actions)) {\n for (const action of actions) {\n granted.add(`${resource}:${String(action)}`);\n }\n }\n }\n return ACCESS_SCOPES.filter((scope) => granted.has(scope));\n}\n"],"mappings":";;;;AAKA,SAAgB,yBACd,QACiC;CACjC,IAAI,OAAO,WAAW,UACpB,OAAO;CAET,MAAM,gBAAgB,8BAA8B,MAAM;CAC1D,IAAI,CAAC,eAAe,SAAS,cAAc,WAAW,WACpD,OAAO;CAET,OAAO;AACT;AAEA,SAAgB,aAAa,OAAwB;CACnD,MAAM,QAAQ,MAAM,MAAM,GAAG;CAC7B,OAAO,MAAM,WAAW,KAAK,MAAM,OAAO,SAAS,KAAK,SAAS,CAAC;AACpE;AAEA,SAAgB,gBAAgB,QAAqD;CACnF,MAAM,SAAS,OAAO;CACtB,MAAM,WAAW,OAAO,aAAa,OAAO;CAC5C,MAAM,QAAQ,OAAO;CACrB,IAAI,OAAO,WAAW,YAAY,OAAO,aAAa,YAAY,OAAO,UAAU,UACjF,OAAO;CAIT,OAAO;EAAE;EAAQ;EAAU;EAAO,QADhC,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,aAAa,IAAI,CAAC;CAC7C;AAC3C;AAEA,eAAsB,eACpB,aACA,cACkB;CAClB,IAAI,gBAAgB,MAClB,OAAO;CAET,OAAO,EAAE,MAAM,aAAa,EAAA,CAAG,SAAS,WAAW;AACrD;AAEA,eAAsB,aACpB,aACA,cACwB;CACxB,OAAO,gBAAgB,QAAQ,CAAE,MAAM,eAAe,aAAa,YAAY,IAC3E,cACA;AACN;AAEA,SAAgB,kBAAkB,UAAkC;CAClE,IAAI,QAAQ;CACZ,IAAI,OAAO,UAAU,UACnB,IAAI;EACF,QAAQ,KAAK,MAAM,KAAK;CAC1B,QAAQ;EACN,OAAO;CACT;CAEF,MAAM,QAAS,OAA+C;CAC9D,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAgB,oBAAoB,QAA4C;CAC9E,MAAM,cAAwC,CAAC;CAC/C,KAAK,MAAM,SAAS,IAAI,IAAI,OAAO,OAAO,aAAa,CAAC,GAAG;EACzD,MAAM,YAAY,MAAM,QAAQ,GAAG;EACnC,MAAM,WAAW,MAAM,MAAM,GAAG,SAAS;EACzC,YAAY,YAAY,CAAC,GAAI,YAAY,aAAa,CAAC,GAAI,MAAM,MAAM,YAAY,CAAC,CAAC;CACvF;CACA,OAAO;AACT;AAEA,SAAgB,oBAAoB,aAAqC;CACvE,IAAI,gBAAgB,QAAQ,OAAO,gBAAgB,UACjD,OAAO,CAAC;CAEV,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,WAAW,GAC1D,IAAI,MAAM,QAAQ,OAAO,GACvB,KAAK,MAAM,UAAU,SACnB,QAAQ,IAAI,GAAG,SAAS,GAAG,OAAO,MAAM,GAAG;CAIjD,OAAO,cAAc,QAAQ,UAAU,QAAQ,IAAI,KAAK,CAAC;AAC3D"}
package/dist/access.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  declare const statement: {
3
3
  readonly course: readonly ["create", "read", "update", "delete"];
4
4
  readonly progress: readonly ["view"];
5
+ readonly community: readonly ["moderate"];
5
6
  readonly organization: readonly ["update", "delete"];
6
7
  readonly member: readonly ["create", "update", "delete"];
7
8
  readonly invitation: readonly ["create", "cancel"];
@@ -12,6 +13,7 @@ declare const ac: {
12
13
  newRole<const TRoleStatements extends import("better-auth/plugins/access").Statements>(statements: import("better-auth/plugins/access").RoleInput<{
13
14
  readonly course: readonly ["create", "read", "update", "delete"];
14
15
  readonly progress: readonly ["view"];
16
+ readonly community: readonly ["moderate"];
15
17
  readonly organization: readonly ["update", "delete"];
16
18
  readonly member: readonly ["create", "update", "delete"];
17
19
  readonly invitation: readonly ["create", "cancel"];
@@ -20,6 +22,7 @@ declare const ac: {
20
22
  }, TRoleStatements>): import("better-auth/plugins/access").Role<import("better-auth/plugins/access").ExactRoleStatements<TRoleStatements>, {
21
23
  readonly course: readonly ["create", "read", "update", "delete"];
22
24
  readonly progress: readonly ["view"];
25
+ readonly community: readonly ["moderate"];
23
26
  readonly organization: readonly ["update", "delete"];
24
27
  readonly member: readonly ["create", "update", "delete"];
25
28
  readonly invitation: readonly ["create", "cancel"];
@@ -29,6 +32,7 @@ declare const ac: {
29
32
  statements: {
30
33
  readonly course: readonly ["create", "read", "update", "delete"];
31
34
  readonly progress: readonly ["view"];
35
+ readonly community: readonly ["moderate"];
32
36
  readonly organization: readonly ["update", "delete"];
33
37
  readonly member: readonly ["create", "update", "delete"];
34
38
  readonly invitation: readonly ["create", "cancel"];
@@ -40,6 +44,7 @@ declare const roles: {
40
44
  owner: import("better-auth/plugins/access").Role<import("better-auth/plugins/access").ExactRoleStatements<{
41
45
  readonly course: ["create", "read", "update", "delete"];
42
46
  readonly progress: ["view"];
47
+ readonly community: ["moderate"];
43
48
  readonly organization: readonly ["update", "delete"];
44
49
  readonly member: readonly ["create", "update", "delete"];
45
50
  readonly invitation: readonly ["create", "cancel"];
@@ -48,6 +53,7 @@ declare const roles: {
48
53
  }>, {
49
54
  readonly course: readonly ["create", "read", "update", "delete"];
50
55
  readonly progress: readonly ["view"];
56
+ readonly community: readonly ["moderate"];
51
57
  readonly organization: readonly ["update", "delete"];
52
58
  readonly member: readonly ["create", "update", "delete"];
53
59
  readonly invitation: readonly ["create", "cancel"];
@@ -57,6 +63,7 @@ declare const roles: {
57
63
  admin: import("better-auth/plugins/access").Role<import("better-auth/plugins/access").ExactRoleStatements<{
58
64
  readonly course: ["create", "read", "update", "delete"];
59
65
  readonly progress: ["view"];
66
+ readonly community: ["moderate"];
60
67
  readonly organization: readonly ["update"];
61
68
  readonly invitation: readonly ["create", "cancel"];
62
69
  readonly member: readonly ["create", "update", "delete"];
@@ -65,6 +72,7 @@ declare const roles: {
65
72
  }>, {
66
73
  readonly course: readonly ["create", "read", "update", "delete"];
67
74
  readonly progress: readonly ["view"];
75
+ readonly community: readonly ["moderate"];
68
76
  readonly organization: readonly ["update", "delete"];
69
77
  readonly member: readonly ["create", "update", "delete"];
70
78
  readonly invitation: readonly ["create", "cancel"];
@@ -74,9 +82,24 @@ declare const roles: {
74
82
  instructor: import("better-auth/plugins/access").Role<import("better-auth/plugins/access").ExactRoleStatements<{
75
83
  readonly course: ["read", "update"];
76
84
  readonly progress: ["view"];
85
+ readonly community: ["moderate"];
77
86
  }>, {
78
87
  readonly course: readonly ["create", "read", "update", "delete"];
79
88
  readonly progress: readonly ["view"];
89
+ readonly community: readonly ["moderate"];
90
+ readonly organization: readonly ["update", "delete"];
91
+ readonly member: readonly ["create", "update", "delete"];
92
+ readonly invitation: readonly ["create", "cancel"];
93
+ readonly team: readonly ["create", "update", "delete"];
94
+ readonly ac: readonly ["create", "read", "update", "delete"];
95
+ }>;
96
+ moderator: import("better-auth/plugins/access").Role<import("better-auth/plugins/access").ExactRoleStatements<{
97
+ readonly course: ["read"];
98
+ readonly community: ["moderate"];
99
+ }>, {
100
+ readonly course: readonly ["create", "read", "update", "delete"];
101
+ readonly progress: readonly ["view"];
102
+ readonly community: readonly ["moderate"];
80
103
  readonly organization: readonly ["update", "delete"];
81
104
  readonly member: readonly ["create", "update", "delete"];
82
105
  readonly invitation: readonly ["create", "cancel"];
@@ -88,6 +111,7 @@ declare const roles: {
88
111
  }>, {
89
112
  readonly course: readonly ["create", "read", "update", "delete"];
90
113
  readonly progress: readonly ["view"];
114
+ readonly community: readonly ["moderate"];
91
115
  readonly organization: readonly ["update", "delete"];
92
116
  readonly member: readonly ["create", "update", "delete"];
93
117
  readonly invitation: readonly ["create", "cancel"];
package/dist/access.js CHANGED
@@ -9,7 +9,8 @@ const statement = {
9
9
  "update",
10
10
  "delete"
11
11
  ],
12
- progress: ["view"]
12
+ progress: ["view"],
13
+ community: ["moderate"]
13
14
  };
14
15
  const ac = createAccessControl(statement);
15
16
  const roles = {
@@ -21,7 +22,8 @@ const roles = {
21
22
  "update",
22
23
  "delete"
23
24
  ],
24
- progress: ["view"]
25
+ progress: ["view"],
26
+ community: ["moderate"]
25
27
  }),
26
28
  admin: ac.newRole({
27
29
  ...adminAc.statements,
@@ -31,11 +33,17 @@ const roles = {
31
33
  "update",
32
34
  "delete"
33
35
  ],
34
- progress: ["view"]
36
+ progress: ["view"],
37
+ community: ["moderate"]
35
38
  }),
36
39
  instructor: ac.newRole({
37
40
  course: ["read", "update"],
38
- progress: ["view"]
41
+ progress: ["view"],
42
+ community: ["moderate"]
43
+ }),
44
+ moderator: ac.newRole({
45
+ course: ["read"],
46
+ community: ["moderate"]
39
47
  }),
40
48
  student: ac.newRole({ course: ["read"] })
41
49
  };
@@ -1 +1 @@
1
- {"version":3,"file":"access.js","names":[],"sources":["../src/access.ts"],"sourcesContent":["// Better Auth org-plugin access control. Better Auth is the system of record for\n// roles; these four roles mirror the domain Role union (core/organizations/roles).\nimport { createAccessControl } from 'better-auth/plugins/access';\nimport { defaultStatements, ownerAc, adminAc } from 'better-auth/plugins/organization/access';\n\nexport const statement = {\n ...defaultStatements,\n course: ['create', 'read', 'update', 'delete'],\n progress: ['view'],\n} as const;\n\nconst ac = createAccessControl(statement);\n\nexport const roles = {\n owner: ac.newRole({\n ...ownerAc.statements,\n course: ['create', 'read', 'update', 'delete'],\n progress: ['view'],\n }),\n admin: ac.newRole({\n ...adminAc.statements,\n course: ['create', 'read', 'update', 'delete'],\n progress: ['view'],\n }),\n instructor: ac.newRole({\n course: ['read', 'update'],\n progress: ['view'],\n }),\n student: ac.newRole({\n course: ['read'],\n }),\n};\n\nexport { ac };\n"],"mappings":";;;AAKA,MAAa,YAAY;CACvB,GAAG;CACH,QAAQ;EAAC;EAAU;EAAQ;EAAU;CAAQ;CAC7C,UAAU,CAAC,MAAM;AACnB;AAEA,MAAM,KAAK,oBAAoB,SAAS;AAExC,MAAa,QAAQ;CACnB,OAAO,GAAG,QAAQ;EAChB,GAAG,QAAQ;EACX,QAAQ;GAAC;GAAU;GAAQ;GAAU;EAAQ;EAC7C,UAAU,CAAC,MAAM;CACnB,CAAC;CACD,OAAO,GAAG,QAAQ;EAChB,GAAG,QAAQ;EACX,QAAQ;GAAC;GAAU;GAAQ;GAAU;EAAQ;EAC7C,UAAU,CAAC,MAAM;CACnB,CAAC;CACD,YAAY,GAAG,QAAQ;EACrB,QAAQ,CAAC,QAAQ,QAAQ;EACzB,UAAU,CAAC,MAAM;CACnB,CAAC;CACD,SAAS,GAAG,QAAQ,EAClB,QAAQ,CAAC,MAAM,EACjB,CAAC;AACH"}
1
+ {"version":3,"file":"access.js","names":[],"sources":["../src/access.ts"],"sourcesContent":["// Better Auth org-plugin access control. Better Auth is the system of record for\n// roles; these roles mirror the domain Role union (core/organizations/roles).\nimport { createAccessControl } from \"better-auth/plugins/access\";\nimport { defaultStatements, ownerAc, adminAc } from \"better-auth/plugins/organization/access\";\n\nexport const statement = {\n ...defaultStatements,\n course: [\"create\", \"read\", \"update\", \"delete\"],\n progress: [\"view\"],\n community: [\"moderate\"],\n} as const;\n\nconst ac = createAccessControl(statement);\n\nexport const roles = {\n owner: ac.newRole({\n ...ownerAc.statements,\n course: [\"create\", \"read\", \"update\", \"delete\"],\n progress: [\"view\"],\n community: [\"moderate\"],\n }),\n admin: ac.newRole({\n ...adminAc.statements,\n course: [\"create\", \"read\", \"update\", \"delete\"],\n progress: [\"view\"],\n community: [\"moderate\"],\n }),\n instructor: ac.newRole({\n course: [\"read\", \"update\"],\n progress: [\"view\"],\n community: [\"moderate\"],\n }),\n moderator: ac.newRole({\n course: [\"read\"],\n community: [\"moderate\"],\n }),\n student: ac.newRole({\n course: [\"read\"],\n }),\n};\n\nexport { ac };\n"],"mappings":";;;AAKA,MAAa,YAAY;CACvB,GAAG;CACH,QAAQ;EAAC;EAAU;EAAQ;EAAU;CAAQ;CAC7C,UAAU,CAAC,MAAM;CACjB,WAAW,CAAC,UAAU;AACxB;AAEA,MAAM,KAAK,oBAAoB,SAAS;AAExC,MAAa,QAAQ;CACnB,OAAO,GAAG,QAAQ;EAChB,GAAG,QAAQ;EACX,QAAQ;GAAC;GAAU;GAAQ;GAAU;EAAQ;EAC7C,UAAU,CAAC,MAAM;EACjB,WAAW,CAAC,UAAU;CACxB,CAAC;CACD,OAAO,GAAG,QAAQ;EAChB,GAAG,QAAQ;EACX,QAAQ;GAAC;GAAU;GAAQ;GAAU;EAAQ;EAC7C,UAAU,CAAC,MAAM;EACjB,WAAW,CAAC,UAAU;CACxB,CAAC;CACD,YAAY,GAAG,QAAQ;EACrB,QAAQ,CAAC,QAAQ,QAAQ;EACzB,UAAU,CAAC,MAAM;EACjB,WAAW,CAAC,UAAU;CACxB,CAAC;CACD,WAAW,GAAG,QAAQ;EACpB,QAAQ,CAAC,MAAM;EACf,WAAW,CAAC,UAAU;CACxB,CAAC;CACD,SAAS,GAAG,QAAQ,EAClB,QAAQ,CAAC,MAAM,EACjB,CAAC;AACH"}