@wrelik/auth 0.2.2 → 2.0.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.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +18 -22
  2. package/CHANGELOG.md +11 -0
  3. package/dist/client/index.d.mts +5 -0
  4. package/dist/client/index.d.ts +5 -0
  5. package/dist/{next.js → client/index.js} +19 -25
  6. package/dist/client/index.mjs +22 -0
  7. package/dist/server/index.d.mts +11 -0
  8. package/dist/server/index.d.ts +11 -0
  9. package/dist/{index.js → server/index.js} +36 -29
  10. package/dist/server/index.mjs +54 -0
  11. package/dist/shared/index.d.mts +21 -0
  12. package/dist/shared/index.d.ts +21 -0
  13. package/dist/shared/index.js +18 -0
  14. package/dist/shared/index.mjs +0 -0
  15. package/package.json +28 -8
  16. package/src/client/index.contract.test.ts +24 -0
  17. package/src/client/index.test.ts +18 -0
  18. package/src/client/index.ts +23 -0
  19. package/src/server/index.test.ts +61 -0
  20. package/src/server/index.ts +60 -0
  21. package/src/shared/index.ts +1 -0
  22. package/src/shared/types.ts +21 -0
  23. package/dist/chunk-HWIZ2Q5U.mjs +0 -17
  24. package/dist/chunk-TF32HGN2.mjs +0 -32
  25. package/dist/index.d.mts +0 -7
  26. package/dist/index.d.ts +0 -7
  27. package/dist/index.mjs +0 -16
  28. package/dist/next.d.mts +0 -5
  29. package/dist/next.d.ts +0 -5
  30. package/dist/next.mjs +0 -14
  31. package/dist/react-native.d.mts +0 -14
  32. package/dist/react-native.d.ts +0 -14
  33. package/dist/react-native.js +0 -77
  34. package/dist/react-native.mjs +0 -27
  35. package/dist/shared-Z5LN5APr.d.mts +0 -11
  36. package/dist/shared-Z5LN5APr.d.ts +0 -11
  37. package/src/index.test.ts +0 -29
  38. package/src/index.ts +0 -21
  39. package/src/next.ts +0 -8
  40. package/src/react-native.ts +0 -28
  41. package/src/shared.test.ts +0 -40
  42. package/src/shared.ts +0 -34
@@ -1,31 +1,27 @@
1
1
 
2
- > @wrelik/auth@0.2.2 build /home/runner/work/wrelik-kit/wrelik-kit/packages/auth
3
- > tsup src/index.ts src/next.ts src/react-native.ts --format cjs,esm --dts --clean
2
+ > @wrelik/auth@2.0.0 build /home/runner/work/wrelik-kit/wrelik-kit/packages/auth
3
+ > tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean
4
4
 
5
- CLI Building entry: src/index.ts, src/next.ts, src/react-native.ts
5
+ CLI Building entry: src/client/index.ts, src/shared/index.ts, src/server/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2019
9
9
  CLI Cleaning output folder
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/index.js 2.28 KB
13
- CJS dist/next.js 1.61 KB
14
- CJS dist/react-native.js 2.36 KB
15
- CJS ⚡️ Build success in 60ms
16
- ESM dist/index.mjs 239.00 B
17
- ESM dist/next.mjs 255.00 B
18
- ESM dist/chunk-HWIZ2Q5U.mjs 391.00 B
19
- ESM dist/react-native.mjs 593.00 B
20
- ESM dist/chunk-TF32HGN2.mjs 799.00 B
21
- ESM ⚡️ Build success in 60ms
12
+ CJS dist/client/index.js 1.62 KB
13
+ CJS dist/shared/index.js 767.00 B
14
+ CJS dist/server/index.js 2.70 KB
15
+ CJS ⚡️ Build success in 62ms
16
+ ESM dist/shared/index.mjs 0 B
17
+ ESM dist/client/index.mjs 631.00 B
18
+ ESM dist/server/index.mjs 1.53 KB
19
+ ESM ⚡️ Build success in 65ms
22
20
  DTS Build start
23
- DTS ⚡️ Build success in 4598ms
24
- DTS dist/index.d.ts 393.00 B
25
- DTS dist/next.d.ts 134.00 B
26
- DTS dist/react-native.d.ts 430.00 B
27
- DTS dist/shared-Z5LN5APr.d.ts 525.00 B
28
- DTS dist/index.d.mts 395.00 B
29
- DTS dist/next.d.mts 135.00 B
30
- DTS dist/react-native.d.mts 432.00 B
31
- DTS dist/shared-Z5LN5APr.d.mts 525.00 B
21
+ DTS ⚡️ Build success in 5529ms
22
+ DTS dist/client/index.d.ts 155.00 B
23
+ DTS dist/server/index.d.ts 684.00 B
24
+ DTS dist/shared/index.d.ts 479.00 B
25
+ DTS dist/client/index.d.mts 156.00 B
26
+ DTS dist/server/index.d.mts 685.00 B
27
+ DTS dist/shared/index.d.mts 479.00 B
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @wrelik/auth
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 818ed58: Refactor runtime packages to strict subpath exports (`/server`, `/client`, `/shared`) with side-effect free entrypoints and hard CI/runtime boundary enforcement.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [818ed58]
12
+ - @wrelik/errors@2.0.0
13
+
3
14
  ## 0.2.2
4
15
 
5
16
  ### Patch Changes
@@ -0,0 +1,5 @@
1
+ import { WorkflowSession } from '../shared/index.mjs';
2
+
3
+ declare function mapClerkToSession(input: unknown): WorkflowSession;
4
+
5
+ export { mapClerkToSession };
@@ -0,0 +1,5 @@
1
+ import { WorkflowSession } from '../shared/index.js';
2
+
3
+ declare function mapClerkToSession(input: unknown): WorkflowSession;
4
+
5
+ export { mapClerkToSession };
@@ -17,37 +17,31 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/next.ts
21
- var next_exports = {};
22
- __export(next_exports, {
23
- getSession: () => getSession
20
+ // src/client/index.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
+ mapClerkToSession: () => mapClerkToSession
24
24
  });
25
- module.exports = __toCommonJS(next_exports);
26
- var import_nextjs = require("@clerk/nextjs");
27
-
28
- // src/shared.ts
29
- var import_errors = require("@wrelik/errors");
30
-
31
- // src/index.ts
32
- function fromClerkAuth(auth2) {
33
- var _a, _b;
34
- if (!auth2 || !auth2.userId) {
25
+ module.exports = __toCommonJS(client_exports);
26
+ function normalizeRoles(value) {
27
+ if (!Array.isArray(value)) {
28
+ return [];
29
+ }
30
+ return value.filter((role) => typeof role === "string");
31
+ }
32
+ function mapClerkToSession(input) {
33
+ var _a, _b, _c, _d;
34
+ const auth = input != null ? input : {};
35
+ if (!auth.userId) {
35
36
  return { userId: null, tenantId: null, roles: [] };
36
37
  }
37
- const roles = ((_b = (_a = auth2.sessionClaims) == null ? void 0 : _a.publicMetadata) == null ? void 0 : _b.roles) || [];
38
38
  return {
39
- userId: auth2.userId,
40
- tenantId: auth2.orgId || null,
41
- roles
39
+ userId: auth.userId,
40
+ tenantId: (_a = auth.orgId) != null ? _a : null,
41
+ roles: normalizeRoles((_d = (_c = (_b = auth.session) == null ? void 0 : _b.user) == null ? void 0 : _c.publicMetadata) == null ? void 0 : _d.roles)
42
42
  };
43
43
  }
44
-
45
- // src/next.ts
46
- function getSession() {
47
- const clerkAuth = (0, import_nextjs.auth)();
48
- return fromClerkAuth(clerkAuth);
49
- }
50
44
  // Annotate the CommonJS export names for ESM import in node:
51
45
  0 && (module.exports = {
52
- getSession
46
+ mapClerkToSession
53
47
  });
@@ -0,0 +1,22 @@
1
+ // src/client/index.ts
2
+ function normalizeRoles(value) {
3
+ if (!Array.isArray(value)) {
4
+ return [];
5
+ }
6
+ return value.filter((role) => typeof role === "string");
7
+ }
8
+ function mapClerkToSession(input) {
9
+ var _a, _b, _c, _d;
10
+ const auth = input != null ? input : {};
11
+ if (!auth.userId) {
12
+ return { userId: null, tenantId: null, roles: [] };
13
+ }
14
+ return {
15
+ userId: auth.userId,
16
+ tenantId: (_a = auth.orgId) != null ? _a : null,
17
+ roles: normalizeRoles((_d = (_c = (_b = auth.session) == null ? void 0 : _b.user) == null ? void 0 : _c.publicMetadata) == null ? void 0 : _d.roles)
18
+ };
19
+ }
20
+ export {
21
+ mapClerkToSession
22
+ };
@@ -0,0 +1,11 @@
1
+ import { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
2
+ import { WorkflowSession } from '../shared/index.mjs';
3
+
4
+ declare function fromClerkAuth(clerkAuth: SignedInAuthObject | SignedOutAuthObject | null): WorkflowSession;
5
+ declare function getSession(): WorkflowSession;
6
+ declare function requireUser(session: WorkflowSession | null): string;
7
+ declare function requireTenant(session: WorkflowSession | null): string;
8
+ declare function hasRole(session: WorkflowSession | null, role: string): boolean;
9
+ declare function requireRole(session: WorkflowSession | null, role: string): void;
10
+
11
+ export { fromClerkAuth, getSession, hasRole, requireRole, requireTenant, requireUser };
@@ -0,0 +1,11 @@
1
+ import { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
2
+ import { WorkflowSession } from '../shared/index.js';
3
+
4
+ declare function fromClerkAuth(clerkAuth: SignedInAuthObject | SignedOutAuthObject | null): WorkflowSession;
5
+ declare function getSession(): WorkflowSession;
6
+ declare function requireUser(session: WorkflowSession | null): string;
7
+ declare function requireTenant(session: WorkflowSession | null): string;
8
+ declare function hasRole(session: WorkflowSession | null, role: string): boolean;
9
+ declare function requireRole(session: WorkflowSession | null, role: string): void;
10
+
11
+ export { fromClerkAuth, getSession, hasRole, requireRole, requireTenant, requireUser };
@@ -17,59 +17,66 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
23
  fromClerkAuth: () => fromClerkAuth,
24
+ getSession: () => getSession,
24
25
  hasRole: () => hasRole,
25
26
  requireRole: () => requireRole,
26
27
  requireTenant: () => requireTenant,
27
28
  requireUser: () => requireUser
28
29
  });
29
- module.exports = __toCommonJS(index_exports);
30
-
31
- // src/shared.ts
32
- var import_errors = require("@wrelik/errors");
30
+ module.exports = __toCommonJS(server_exports);
31
+ var import_nextjs = require("@clerk/nextjs");
32
+ var import_shared = require("@wrelik/errors/shared");
33
+ function normalizeRoles(value) {
34
+ if (!Array.isArray(value)) {
35
+ return [];
36
+ }
37
+ return value.filter((role) => typeof role === "string");
38
+ }
39
+ function fromClerkAuth(clerkAuth) {
40
+ var _a, _b, _c;
41
+ if (!(clerkAuth == null ? void 0 : clerkAuth.userId)) {
42
+ return { userId: null, tenantId: null, roles: [] };
43
+ }
44
+ const authLike = clerkAuth;
45
+ const claims = (_a = authLike.sessionClaims) != null ? _a : {};
46
+ return {
47
+ userId: clerkAuth.userId,
48
+ tenantId: (_b = clerkAuth.orgId) != null ? _b : null,
49
+ roles: normalizeRoles((_c = claims.publicMetadata) == null ? void 0 : _c.roles)
50
+ };
51
+ }
52
+ function getSession() {
53
+ return fromClerkAuth((0, import_nextjs.auth)());
54
+ }
33
55
  function requireUser(session) {
34
- if (!session || !session.userId) {
35
- throw new import_errors.AuthRequiredError();
56
+ if (!(session == null ? void 0 : session.userId)) {
57
+ throw new import_shared.AuthRequiredError();
36
58
  }
37
59
  return session.userId;
38
60
  }
39
61
  function requireTenant(session) {
40
62
  requireUser(session);
41
- if (!session || !session.tenantId) {
42
- throw new import_errors.TenantRequiredError();
63
+ if (!(session == null ? void 0 : session.tenantId)) {
64
+ throw new import_shared.TenantRequiredError();
43
65
  }
44
66
  return session.tenantId;
45
67
  }
46
68
  function hasRole(session, role) {
47
- if (!session || !session.userId) return false;
48
- if (!Array.isArray(session.roles)) return false;
49
- return session.roles.includes(role);
69
+ return Boolean((session == null ? void 0 : session.userId) && Array.isArray(session.roles) && session.roles.includes(role));
50
70
  }
51
71
  function requireRole(session, role) {
52
72
  if (!hasRole(session, role)) {
53
- throw new import_errors.PermissionDeniedError(`Role ${role} required`);
73
+ throw new import_shared.PermissionDeniedError(`Role ${role} required`);
54
74
  }
55
75
  }
56
-
57
- // src/index.ts
58
- function fromClerkAuth(auth) {
59
- var _a, _b;
60
- if (!auth || !auth.userId) {
61
- return { userId: null, tenantId: null, roles: [] };
62
- }
63
- const roles = ((_b = (_a = auth.sessionClaims) == null ? void 0 : _a.publicMetadata) == null ? void 0 : _b.roles) || [];
64
- return {
65
- userId: auth.userId,
66
- tenantId: auth.orgId || null,
67
- roles
68
- };
69
- }
70
76
  // Annotate the CommonJS export names for ESM import in node:
71
77
  0 && (module.exports = {
72
78
  fromClerkAuth,
79
+ getSession,
73
80
  hasRole,
74
81
  requireRole,
75
82
  requireTenant,
@@ -0,0 +1,54 @@
1
+ // src/server/index.ts
2
+ import { auth } from "@clerk/nextjs";
3
+ import { AuthRequiredError, PermissionDeniedError, TenantRequiredError } from "@wrelik/errors/shared";
4
+ function normalizeRoles(value) {
5
+ if (!Array.isArray(value)) {
6
+ return [];
7
+ }
8
+ return value.filter((role) => typeof role === "string");
9
+ }
10
+ function fromClerkAuth(clerkAuth) {
11
+ var _a, _b, _c;
12
+ if (!(clerkAuth == null ? void 0 : clerkAuth.userId)) {
13
+ return { userId: null, tenantId: null, roles: [] };
14
+ }
15
+ const authLike = clerkAuth;
16
+ const claims = (_a = authLike.sessionClaims) != null ? _a : {};
17
+ return {
18
+ userId: clerkAuth.userId,
19
+ tenantId: (_b = clerkAuth.orgId) != null ? _b : null,
20
+ roles: normalizeRoles((_c = claims.publicMetadata) == null ? void 0 : _c.roles)
21
+ };
22
+ }
23
+ function getSession() {
24
+ return fromClerkAuth(auth());
25
+ }
26
+ function requireUser(session) {
27
+ if (!(session == null ? void 0 : session.userId)) {
28
+ throw new AuthRequiredError();
29
+ }
30
+ return session.userId;
31
+ }
32
+ function requireTenant(session) {
33
+ requireUser(session);
34
+ if (!(session == null ? void 0 : session.tenantId)) {
35
+ throw new TenantRequiredError();
36
+ }
37
+ return session.tenantId;
38
+ }
39
+ function hasRole(session, role) {
40
+ return Boolean((session == null ? void 0 : session.userId) && Array.isArray(session.roles) && session.roles.includes(role));
41
+ }
42
+ function requireRole(session, role) {
43
+ if (!hasRole(session, role)) {
44
+ throw new PermissionDeniedError(`Role ${role} required`);
45
+ }
46
+ }
47
+ export {
48
+ fromClerkAuth,
49
+ getSession,
50
+ hasRole,
51
+ requireRole,
52
+ requireTenant,
53
+ requireUser
54
+ };
@@ -0,0 +1,21 @@
1
+ interface WorkflowSession {
2
+ userId: string | null;
3
+ tenantId: string | null;
4
+ roles: string[];
5
+ }
6
+ interface ClerkSessionLike {
7
+ userId?: string | null;
8
+ orgId?: string | null;
9
+ sessionClaims?: unknown;
10
+ }
11
+ interface ClerkClientSessionLike {
12
+ userId?: string | null;
13
+ orgId?: string | null;
14
+ session?: {
15
+ user?: {
16
+ publicMetadata?: unknown;
17
+ };
18
+ };
19
+ }
20
+
21
+ export type { ClerkClientSessionLike, ClerkSessionLike, WorkflowSession };
@@ -0,0 +1,21 @@
1
+ interface WorkflowSession {
2
+ userId: string | null;
3
+ tenantId: string | null;
4
+ roles: string[];
5
+ }
6
+ interface ClerkSessionLike {
7
+ userId?: string | null;
8
+ orgId?: string | null;
9
+ sessionClaims?: unknown;
10
+ }
11
+ interface ClerkClientSessionLike {
12
+ userId?: string | null;
13
+ orgId?: string | null;
14
+ session?: {
15
+ user?: {
16
+ publicMetadata?: unknown;
17
+ };
18
+ };
19
+ }
20
+
21
+ export type { ClerkClientSessionLike, ClerkSessionLike, WorkflowSession };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/shared/index.ts
17
+ var shared_exports = {};
18
+ module.exports = __toCommonJS(shared_exports);
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrelik/auth",
3
- "version": "0.2.2",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,16 +9,28 @@
9
9
  "url": "https://github.com/lwhite702/wrelik-kit.git",
10
10
  "directory": "packages/auth"
11
11
  },
12
- "main": "./dist/index.js",
13
- "types": "./dist/index.d.ts",
12
+ "sideEffects": false,
14
13
  "exports": {
15
- ".": "./dist/index.js",
16
- "./next": "./dist/next.js",
17
- "./react-native": "./dist/react-native.js"
14
+ "./server": {
15
+ "types": "./dist/server/index.d.ts",
16
+ "import": "./dist/server/index.mjs",
17
+ "require": "./dist/server/index.js"
18
+ },
19
+ "./client": {
20
+ "types": "./dist/client/index.d.ts",
21
+ "import": "./dist/client/index.mjs",
22
+ "require": "./dist/client/index.js"
23
+ },
24
+ "./shared": {
25
+ "types": "./dist/shared/index.d.ts",
26
+ "import": "./dist/shared/index.mjs",
27
+ "require": "./dist/shared/index.js"
28
+ },
29
+ "./package.json": "./package.json"
18
30
  },
19
31
  "dependencies": {
20
32
  "@clerk/backend": "^0.38.0",
21
- "@wrelik/errors": "0.2.1"
33
+ "@wrelik/errors": "2.0.0"
22
34
  },
23
35
  "peerDependencies": {
24
36
  "@clerk/nextjs": "^4.29.0"
@@ -29,9 +41,17 @@
29
41
  "@wrelik/eslint-config": "0.1.1",
30
42
  "@wrelik/tsconfig": "0.1.1"
31
43
  },
44
+ "wrelik": {
45
+ "runtimes": [
46
+ "server",
47
+ "client",
48
+ "shared"
49
+ ]
50
+ },
32
51
  "scripts": {
33
- "build": "tsup src/index.ts src/next.ts src/react-native.ts --format cjs,esm --dts --clean",
52
+ "build": "tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean",
34
53
  "lint": "eslint src/",
54
+ "typecheck": "tsc --noEmit",
35
55
  "test": "vitest run --passWithNoTests"
36
56
  }
37
57
  }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { mapClerkToSession } from './index';
3
+
4
+ describe('@wrelik/auth/client contract', () => {
5
+ it('normalizes mixed roles from Clerk client payload', () => {
6
+ const session = mapClerkToSession({
7
+ userId: 'u1',
8
+ orgId: 't1',
9
+ session: {
10
+ user: {
11
+ publicMetadata: {
12
+ roles: ['admin', 42, 'owner'],
13
+ },
14
+ },
15
+ },
16
+ });
17
+
18
+ expect(session).toEqual({
19
+ userId: 'u1',
20
+ tenantId: 't1',
21
+ roles: ['admin', 'owner'],
22
+ });
23
+ });
24
+ });
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { mapClerkToSession } from './index';
3
+
4
+ describe('@wrelik/auth/client', () => {
5
+ it('maps unknown auth input to a safe empty session', () => {
6
+ expect(mapClerkToSession(null)).toEqual({ userId: null, tenantId: null, roles: [] });
7
+ });
8
+
9
+ it('maps role metadata without permission checks', () => {
10
+ const session = mapClerkToSession({
11
+ userId: 'u1',
12
+ orgId: 'o1',
13
+ session: { user: { publicMetadata: { roles: ['admin', 1] } } },
14
+ });
15
+
16
+ expect(session).toEqual({ userId: 'u1', tenantId: 'o1', roles: ['admin'] });
17
+ });
18
+ });
@@ -0,0 +1,23 @@
1
+ import type { ClerkClientSessionLike, WorkflowSession } from '../shared';
2
+
3
+ function normalizeRoles(value: unknown): string[] {
4
+ if (!Array.isArray(value)) {
5
+ return [];
6
+ }
7
+
8
+ return value.filter((role): role is string => typeof role === 'string');
9
+ }
10
+
11
+ export function mapClerkToSession(input: unknown): WorkflowSession {
12
+ const auth = (input ?? {}) as ClerkClientSessionLike;
13
+
14
+ if (!auth.userId) {
15
+ return { userId: null, tenantId: null, roles: [] };
16
+ }
17
+
18
+ return {
19
+ userId: auth.userId,
20
+ tenantId: auth.orgId ?? null,
21
+ roles: normalizeRoles((auth.session?.user?.publicMetadata as { roles?: unknown } | undefined)?.roles),
22
+ };
23
+ }
@@ -0,0 +1,61 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { AuthRequiredError, PermissionDeniedError, TenantRequiredError } from '@wrelik/errors/shared';
3
+ import { fromClerkAuth, hasRole, requireRole, requireTenant, requireUser } from './index';
4
+
5
+ afterEach(() => {
6
+ vi.resetModules();
7
+ vi.clearAllMocks();
8
+ });
9
+
10
+ describe('@wrelik/auth/server', () => {
11
+ it('requires authenticated users and tenant contexts', () => {
12
+ expect(() => requireUser(null)).toThrow(AuthRequiredError);
13
+ expect(() => requireTenant({ userId: 'u1', tenantId: null, roles: [] })).toThrow(TenantRequiredError);
14
+ });
15
+
16
+ it('checks role membership with runtime-shape safety', () => {
17
+ expect(hasRole({ userId: 'u1', tenantId: 't1', roles: ['admin'] }, 'admin')).toBe(true);
18
+ expect(() => requireRole({ userId: 'u1', tenantId: 't1', roles: [] }, 'admin')).toThrow(
19
+ PermissionDeniedError,
20
+ );
21
+ });
22
+
23
+ it('maps Clerk auth payloads defensively', () => {
24
+ const session = fromClerkAuth({
25
+ userId: 'u1',
26
+ orgId: 'o1',
27
+ sessionClaims: { publicMetadata: { roles: ['admin', 1] } },
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ } as any);
30
+
31
+ expect(session).toEqual({ userId: 'u1', tenantId: 'o1', roles: ['admin'] });
32
+ });
33
+
34
+ it('retrieves and normalizes sessions from Clerk auth()', async () => {
35
+ vi.resetModules();
36
+ vi.doMock('@clerk/nextjs', () => ({
37
+ auth: () => ({
38
+ userId: 'u1',
39
+ orgId: 'o1',
40
+ sessionClaims: { publicMetadata: { roles: ['admin', 123] } },
41
+ }),
42
+ }));
43
+
44
+ const mod = await import('./index');
45
+ expect(mod.getSession()).toEqual({ userId: 'u1', tenantId: 'o1', roles: ['admin'] });
46
+ });
47
+
48
+ it('returns anonymous session when Clerk auth() is signed out', async () => {
49
+ vi.resetModules();
50
+ vi.doMock('@clerk/nextjs', () => ({
51
+ auth: () => ({ userId: null, orgId: null, sessionClaims: {} }),
52
+ }));
53
+
54
+ const mod = await import('./index');
55
+ expect(mod.getSession()).toEqual({ userId: null, tenantId: null, roles: [] });
56
+ });
57
+
58
+ it('does not expose a root package import', () => {
59
+ expect(() => require.resolve('@wrelik/auth')).toThrow();
60
+ });
61
+ });
@@ -0,0 +1,60 @@
1
+ /* eslint-disable no-restricted-imports */
2
+ import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
3
+ import { auth } from '@clerk/nextjs';
4
+ import { AuthRequiredError, PermissionDeniedError, TenantRequiredError } from '@wrelik/errors/shared';
5
+ import type { ClerkSessionLike, WorkflowSession } from '../shared';
6
+
7
+ function normalizeRoles(value: unknown): string[] {
8
+ if (!Array.isArray(value)) {
9
+ return [];
10
+ }
11
+
12
+ return value.filter((role): role is string => typeof role === 'string');
13
+ }
14
+
15
+ export function fromClerkAuth(clerkAuth: SignedInAuthObject | SignedOutAuthObject | null): WorkflowSession {
16
+ if (!clerkAuth?.userId) {
17
+ return { userId: null, tenantId: null, roles: [] };
18
+ }
19
+
20
+ const authLike: ClerkSessionLike = clerkAuth;
21
+ const claims = (authLike.sessionClaims as { publicMetadata?: { roles?: unknown } } | undefined) ?? {};
22
+
23
+ return {
24
+ userId: clerkAuth.userId,
25
+ tenantId: clerkAuth.orgId ?? null,
26
+ roles: normalizeRoles(claims.publicMetadata?.roles),
27
+ };
28
+ }
29
+
30
+ export function getSession(): WorkflowSession {
31
+ return fromClerkAuth(auth());
32
+ }
33
+
34
+ export function requireUser(session: WorkflowSession | null): string {
35
+ if (!session?.userId) {
36
+ throw new AuthRequiredError();
37
+ }
38
+
39
+ return session.userId;
40
+ }
41
+
42
+ export function requireTenant(session: WorkflowSession | null): string {
43
+ requireUser(session);
44
+
45
+ if (!session?.tenantId) {
46
+ throw new TenantRequiredError();
47
+ }
48
+
49
+ return session.tenantId;
50
+ }
51
+
52
+ export function hasRole(session: WorkflowSession | null, role: string): boolean {
53
+ return Boolean(session?.userId && Array.isArray(session.roles) && session.roles.includes(role));
54
+ }
55
+
56
+ export function requireRole(session: WorkflowSession | null, role: string): void {
57
+ if (!hasRole(session, role)) {
58
+ throw new PermissionDeniedError(`Role ${role} required`);
59
+ }
60
+ }
@@ -0,0 +1 @@
1
+ export type { WorkflowSession, ClerkSessionLike, ClerkClientSessionLike } from './types';
@@ -0,0 +1,21 @@
1
+ export interface WorkflowSession {
2
+ userId: string | null;
3
+ tenantId: string | null;
4
+ roles: string[];
5
+ }
6
+
7
+ export interface ClerkSessionLike {
8
+ userId?: string | null;
9
+ orgId?: string | null;
10
+ sessionClaims?: unknown;
11
+ }
12
+
13
+ export interface ClerkClientSessionLike {
14
+ userId?: string | null;
15
+ orgId?: string | null;
16
+ session?: {
17
+ user?: {
18
+ publicMetadata?: unknown;
19
+ };
20
+ };
21
+ }
@@ -1,17 +0,0 @@
1
- // src/index.ts
2
- function fromClerkAuth(auth) {
3
- var _a, _b;
4
- if (!auth || !auth.userId) {
5
- return { userId: null, tenantId: null, roles: [] };
6
- }
7
- const roles = ((_b = (_a = auth.sessionClaims) == null ? void 0 : _a.publicMetadata) == null ? void 0 : _b.roles) || [];
8
- return {
9
- userId: auth.userId,
10
- tenantId: auth.orgId || null,
11
- roles
12
- };
13
- }
14
-
15
- export {
16
- fromClerkAuth
17
- };
@@ -1,32 +0,0 @@
1
- // src/shared.ts
2
- import { AuthRequiredError, PermissionDeniedError, TenantRequiredError } from "@wrelik/errors";
3
- function requireUser(session) {
4
- if (!session || !session.userId) {
5
- throw new AuthRequiredError();
6
- }
7
- return session.userId;
8
- }
9
- function requireTenant(session) {
10
- requireUser(session);
11
- if (!session || !session.tenantId) {
12
- throw new TenantRequiredError();
13
- }
14
- return session.tenantId;
15
- }
16
- function hasRole(session, role) {
17
- if (!session || !session.userId) return false;
18
- if (!Array.isArray(session.roles)) return false;
19
- return session.roles.includes(role);
20
- }
21
- function requireRole(session, role) {
22
- if (!hasRole(session, role)) {
23
- throw new PermissionDeniedError(`Role ${role} required`);
24
- }
25
- }
26
-
27
- export {
28
- requireUser,
29
- requireTenant,
30
- hasRole,
31
- requireRole
32
- };
package/dist/index.d.mts DELETED
@@ -1,7 +0,0 @@
1
- import { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
2
- import { W as WorkflowSession } from './shared-Z5LN5APr.mjs';
3
- export { h as hasRole, r as requireRole, a as requireTenant, b as requireUser } from './shared-Z5LN5APr.mjs';
4
-
5
- declare function fromClerkAuth(auth: SignedInAuthObject | SignedOutAuthObject | null): WorkflowSession;
6
-
7
- export { WorkflowSession, fromClerkAuth };
package/dist/index.d.ts DELETED
@@ -1,7 +0,0 @@
1
- import { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
2
- import { W as WorkflowSession } from './shared-Z5LN5APr.js';
3
- export { h as hasRole, r as requireRole, a as requireTenant, b as requireUser } from './shared-Z5LN5APr.js';
4
-
5
- declare function fromClerkAuth(auth: SignedInAuthObject | SignedOutAuthObject | null): WorkflowSession;
6
-
7
- export { WorkflowSession, fromClerkAuth };
package/dist/index.mjs DELETED
@@ -1,16 +0,0 @@
1
- import {
2
- fromClerkAuth
3
- } from "./chunk-HWIZ2Q5U.mjs";
4
- import {
5
- hasRole,
6
- requireRole,
7
- requireTenant,
8
- requireUser
9
- } from "./chunk-TF32HGN2.mjs";
10
- export {
11
- fromClerkAuth,
12
- hasRole,
13
- requireRole,
14
- requireTenant,
15
- requireUser
16
- };
package/dist/next.d.mts DELETED
@@ -1,5 +0,0 @@
1
- import { W as WorkflowSession } from './shared-Z5LN5APr.mjs';
2
-
3
- declare function getSession(): WorkflowSession;
4
-
5
- export { getSession };
package/dist/next.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import { W as WorkflowSession } from './shared-Z5LN5APr.js';
2
-
3
- declare function getSession(): WorkflowSession;
4
-
5
- export { getSession };
package/dist/next.mjs DELETED
@@ -1,14 +0,0 @@
1
- import {
2
- fromClerkAuth
3
- } from "./chunk-HWIZ2Q5U.mjs";
4
- import "./chunk-TF32HGN2.mjs";
5
-
6
- // src/next.ts
7
- import { auth } from "@clerk/nextjs";
8
- function getSession() {
9
- const clerkAuth = auth();
10
- return fromClerkAuth(clerkAuth);
11
- }
12
- export {
13
- getSession
14
- };
@@ -1,14 +0,0 @@
1
- import { W as WorkflowSession } from './shared-Z5LN5APr.mjs';
2
- export { h as hasRole, r as requireRole, a as requireTenant, b as requireUser } from './shared-Z5LN5APr.mjs';
3
-
4
- declare function mapClerkToSession(auth: {
5
- userId?: string | null;
6
- orgId?: string | null;
7
- session?: {
8
- user?: {
9
- publicMetadata?: any;
10
- };
11
- };
12
- } | null): WorkflowSession;
13
-
14
- export { WorkflowSession, mapClerkToSession };
@@ -1,14 +0,0 @@
1
- import { W as WorkflowSession } from './shared-Z5LN5APr.js';
2
- export { h as hasRole, r as requireRole, a as requireTenant, b as requireUser } from './shared-Z5LN5APr.js';
3
-
4
- declare function mapClerkToSession(auth: {
5
- userId?: string | null;
6
- orgId?: string | null;
7
- session?: {
8
- user?: {
9
- publicMetadata?: any;
10
- };
11
- };
12
- } | null): WorkflowSession;
13
-
14
- export { WorkflowSession, mapClerkToSession };
@@ -1,77 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/react-native.ts
21
- var react_native_exports = {};
22
- __export(react_native_exports, {
23
- hasRole: () => hasRole,
24
- mapClerkToSession: () => mapClerkToSession,
25
- requireRole: () => requireRole,
26
- requireTenant: () => requireTenant,
27
- requireUser: () => requireUser
28
- });
29
- module.exports = __toCommonJS(react_native_exports);
30
-
31
- // src/shared.ts
32
- var import_errors = require("@wrelik/errors");
33
- function requireUser(session) {
34
- if (!session || !session.userId) {
35
- throw new import_errors.AuthRequiredError();
36
- }
37
- return session.userId;
38
- }
39
- function requireTenant(session) {
40
- requireUser(session);
41
- if (!session || !session.tenantId) {
42
- throw new import_errors.TenantRequiredError();
43
- }
44
- return session.tenantId;
45
- }
46
- function hasRole(session, role) {
47
- if (!session || !session.userId) return false;
48
- if (!Array.isArray(session.roles)) return false;
49
- return session.roles.includes(role);
50
- }
51
- function requireRole(session, role) {
52
- if (!hasRole(session, role)) {
53
- throw new import_errors.PermissionDeniedError(`Role ${role} required`);
54
- }
55
- }
56
-
57
- // src/react-native.ts
58
- function mapClerkToSession(auth) {
59
- var _a, _b, _c;
60
- if (!auth || !auth.userId) {
61
- return { userId: null, tenantId: null, roles: [] };
62
- }
63
- const roles = ((_c = (_b = (_a = auth.session) == null ? void 0 : _a.user) == null ? void 0 : _b.publicMetadata) == null ? void 0 : _c.roles) || [];
64
- return {
65
- userId: auth.userId,
66
- tenantId: auth.orgId || null,
67
- roles
68
- };
69
- }
70
- // Annotate the CommonJS export names for ESM import in node:
71
- 0 && (module.exports = {
72
- hasRole,
73
- mapClerkToSession,
74
- requireRole,
75
- requireTenant,
76
- requireUser
77
- });
@@ -1,27 +0,0 @@
1
- import {
2
- hasRole,
3
- requireRole,
4
- requireTenant,
5
- requireUser
6
- } from "./chunk-TF32HGN2.mjs";
7
-
8
- // src/react-native.ts
9
- function mapClerkToSession(auth) {
10
- var _a, _b, _c;
11
- if (!auth || !auth.userId) {
12
- return { userId: null, tenantId: null, roles: [] };
13
- }
14
- const roles = ((_c = (_b = (_a = auth.session) == null ? void 0 : _a.user) == null ? void 0 : _b.publicMetadata) == null ? void 0 : _c.roles) || [];
15
- return {
16
- userId: auth.userId,
17
- tenantId: auth.orgId || null,
18
- roles
19
- };
20
- }
21
- export {
22
- hasRole,
23
- mapClerkToSession,
24
- requireRole,
25
- requireTenant,
26
- requireUser
27
- };
@@ -1,11 +0,0 @@
1
- interface WorkflowSession {
2
- userId: string | null;
3
- tenantId: string | null;
4
- roles: string[];
5
- }
6
- declare function requireUser(session: WorkflowSession | null): string;
7
- declare function requireTenant(session: WorkflowSession | null): string;
8
- declare function hasRole(session: WorkflowSession | null, role: string): boolean;
9
- declare function requireRole(session: WorkflowSession | null, role: string): void;
10
-
11
- export { type WorkflowSession as W, requireTenant as a, requireUser as b, hasRole as h, requireRole as r };
@@ -1,11 +0,0 @@
1
- interface WorkflowSession {
2
- userId: string | null;
3
- tenantId: string | null;
4
- roles: string[];
5
- }
6
- declare function requireUser(session: WorkflowSession | null): string;
7
- declare function requireTenant(session: WorkflowSession | null): string;
8
- declare function hasRole(session: WorkflowSession | null, role: string): boolean;
9
- declare function requireRole(session: WorkflowSession | null, role: string): void;
10
-
11
- export { type WorkflowSession as W, requireTenant as a, requireUser as b, hasRole as h, requireRole as r };
package/src/index.test.ts DELETED
@@ -1,29 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { requireUser, fromClerkAuth } from './index';
3
- import { AuthRequiredError } from '@wrelik/errors';
4
-
5
- describe('@wrelik/auth', () => {
6
- it('should throw AuthRequiredError if user is null', () => {
7
- const session = { userId: null, tenantId: null, roles: [] };
8
- expect(() => requireUser(session)).toThrow(AuthRequiredError);
9
- });
10
-
11
- it('should return userId if session is valid', () => {
12
- const session = { userId: 'user_123', tenantId: null, roles: [] };
13
- expect(requireUser(session)).toBe('user_123');
14
- });
15
-
16
- it('should correctly parse Clerk auth object', () => {
17
- const clerkAuth = {
18
- userId: 'user_123',
19
- orgId: 'org_456',
20
- sessionClaims: { publicMetadata: { roles: ['admin'] } },
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- } as any;
23
-
24
- const session = fromClerkAuth(clerkAuth);
25
- expect(session.userId).toBe('user_123');
26
- expect(session.tenantId).toBe('org_456');
27
- expect(session.roles).toContain('admin');
28
- });
29
- });
package/src/index.ts DELETED
@@ -1,21 +0,0 @@
1
- import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend';
2
- import { type WorkflowSession } from './shared';
3
-
4
- export * from './shared';
5
-
6
- export function fromClerkAuth(
7
- auth: SignedInAuthObject | SignedOutAuthObject | null,
8
- ): WorkflowSession {
9
- if (!auth || !auth.userId) {
10
- return { userId: null, tenantId: null, roles: [] };
11
- }
12
-
13
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
- const roles = ((auth.sessionClaims?.publicMetadata as any)?.roles as string[]) || [];
15
-
16
- return {
17
- userId: auth.userId,
18
- tenantId: auth.orgId || null,
19
- roles,
20
- };
21
- }
package/src/next.ts DELETED
@@ -1,8 +0,0 @@
1
- /* eslint-disable no-restricted-imports */
2
- import { auth } from '@clerk/nextjs';
3
- import { fromClerkAuth, type WorkflowSession } from './index';
4
-
5
- export function getSession(): WorkflowSession {
6
- const clerkAuth = auth();
7
- return fromClerkAuth(clerkAuth);
8
- }
@@ -1,28 +0,0 @@
1
- import { type WorkflowSession } from './shared';
2
-
3
- export * from './shared';
4
-
5
- // We accept the `useAuth` or `useUser` return value or similar from Clerk Expo
6
- // but to be loose coupling, we defined it as any with some shape checks if needed.
7
- // However, the requirement says "converts the Clerk Expo session/user representation".
8
- export function mapClerkToSession(
9
- auth: {
10
- userId?: string | null;
11
- orgId?: string | null;
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- session?: { user?: { publicMetadata?: any } };
14
- } | null,
15
- ): WorkflowSession {
16
- if (!auth || !auth.userId) {
17
- return { userId: null, tenantId: null, roles: [] };
18
- }
19
-
20
- // The roles might be in publicMetadata of the user object in session
21
- const roles = (auth.session?.user?.publicMetadata?.roles as string[]) || [];
22
-
23
- return {
24
- userId: auth.userId,
25
- tenantId: auth.orgId || null,
26
- roles,
27
- };
28
- }
@@ -1,40 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { requireUser, requireTenant, hasRole } from './shared';
3
-
4
- describe('Auth Shared', () => {
5
- it('requireUser throws if no user', () => {
6
- expect(() => requireUser(null)).toThrow('Authentication required');
7
- expect(() => requireUser({ userId: null, tenantId: null, roles: [] })).toThrow(
8
- 'Authentication required',
9
- );
10
- });
11
-
12
- it('requireUser returns userId', () => {
13
- expect(requireUser({ userId: 'u1', tenantId: null, roles: [] })).toBe('u1');
14
- });
15
-
16
- it('requireTenant throws if no tenant', () => {
17
- expect(() => requireTenant({ userId: 'u1', tenantId: null, roles: [] })).toThrow(
18
- 'Tenant context required',
19
- );
20
- });
21
-
22
- it('hasRole checks roles', () => {
23
- const session = { userId: 'u1', tenantId: 't1', roles: ['admin'] };
24
- expect(hasRole(session, 'admin')).toBe(true);
25
- expect(hasRole(session, 'user')).toBe(false);
26
- });
27
-
28
- it('does not grant role access when roles has invalid runtime shape', () => {
29
- const malformedSession = {
30
- userId: 'u1',
31
- tenantId: 't1',
32
- // Simulates untrusted runtime payload, despite the TypeScript type.
33
- roles: 'admin',
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- } as any;
36
-
37
- expect(hasRole(malformedSession, 'admin')).toBe(false);
38
- expect(hasRole(malformedSession, 'min')).toBe(false);
39
- });
40
- });
package/src/shared.ts DELETED
@@ -1,34 +0,0 @@
1
- import { AuthRequiredError, PermissionDeniedError, TenantRequiredError } from '@wrelik/errors';
2
-
3
- export interface WorkflowSession {
4
- userId: string | null;
5
- tenantId: string | null;
6
- roles: string[];
7
- }
8
-
9
- export function requireUser(session: WorkflowSession | null): string {
10
- if (!session || !session.userId) {
11
- throw new AuthRequiredError();
12
- }
13
- return session.userId;
14
- }
15
-
16
- export function requireTenant(session: WorkflowSession | null): string {
17
- requireUser(session);
18
- if (!session || !session.tenantId) {
19
- throw new TenantRequiredError();
20
- }
21
- return session.tenantId;
22
- }
23
-
24
- export function hasRole(session: WorkflowSession | null, role: string): boolean {
25
- if (!session || !session.userId) return false;
26
- if (!Array.isArray(session.roles)) return false;
27
- return session.roles.includes(role);
28
- }
29
-
30
- export function requireRole(session: WorkflowSession | null, role: string) {
31
- if (!hasRole(session, role)) {
32
- throw new PermissionDeniedError(`Role ${role} required`);
33
- }
34
- }