canopycms-auth-clerk 0.0.0 → 0.0.2
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.
- package/dist/clerk-plugin.d.ts +3 -0
- package/dist/clerk-plugin.d.ts.map +1 -1
- package/dist/clerk-plugin.js +24 -0
- package/dist/clerk-plugin.js.map +1 -1
- package/dist/jwt-verifier.d.ts +3 -0
- package/dist/jwt-verifier.d.ts.map +1 -1
- package/dist/jwt-verifier.js +3 -0
- package/dist/jwt-verifier.js.map +1 -1
- package/package.json +3 -4
- package/src/cache-writer.ts +0 -152
- package/src/clerk-plugin.test.ts +0 -385
- package/src/clerk-plugin.ts +0 -315
- package/src/client.ts +0 -38
- package/src/index.ts +0 -6
- package/src/jwt-verifier.ts +0 -62
package/dist/clerk-plugin.d.ts
CHANGED
|
@@ -35,6 +35,9 @@ export declare class ClerkAuthPlugin implements AuthPlugin {
|
|
|
35
35
|
private config;
|
|
36
36
|
private clerkClient;
|
|
37
37
|
constructor(config?: ClerkAuthConfig);
|
|
38
|
+
verifyTokenOnly(context: unknown): Promise<{
|
|
39
|
+
userId: string;
|
|
40
|
+
} | null>;
|
|
38
41
|
authenticate(context: unknown): Promise<AuthenticationResult>;
|
|
39
42
|
searchUsers(query: string, limit?: number): Promise<UserSearchResult[]>;
|
|
40
43
|
getUserMetadata(userId: string): Promise<UserSearchResult | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clerk-plugin.d.ts","sourceRoot":"","sources":["../src/clerk-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC3F,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAElC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAmED;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,WAAW,KAAG,MAAM,GAAG,IAiB5D,CAAA;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAChD,OAAO,CAAC,MAAM,CAIb;IACD,OAAO,CAAC,WAAW,CAAsC;gBAE7C,MAAM,GAAE,eAAoB;IAyBlC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAuE7D,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAuBnE,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAgBjE,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAqBhE,UAAU,CAAC,KAAK,SAAK,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CAqBvD;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAiB,CAAC,eAAe,CAEpE,CAAA"}
|
|
1
|
+
{"version":3,"file":"clerk-plugin.d.ts","sourceRoot":"","sources":["../src/clerk-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AACnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC3F,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjE,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAElC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAmED;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,WAAW,KAAG,MAAM,GAAG,IAiB5D,CAAA;AAED;;;GAGG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAChD,OAAO,CAAC,MAAM,CAIb;IACD,OAAO,CAAC,WAAW,CAAsC;gBAE7C,MAAM,GAAE,eAAoB;IAyBlC,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAwBrE,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAuE7D,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAuBnE,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAgBjE,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAqBhE,UAAU,CAAC,KAAK,SAAK,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;CAqBvD;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,iBAAiB,CAAC,eAAe,CAEpE,CAAA"}
|
package/dist/clerk-plugin.js
CHANGED
|
@@ -68,6 +68,30 @@ export class ClerkAuthPlugin {
|
|
|
68
68
|
};
|
|
69
69
|
this.clerkClient = createClerkClient({ secretKey });
|
|
70
70
|
}
|
|
71
|
+
async verifyTokenOnly(context) {
|
|
72
|
+
if (!this.config.jwtKey) {
|
|
73
|
+
throw new Error('ClerkAuthPlugin: jwtKey is required for verifyTokenOnly() (networkless JWT verification). ' +
|
|
74
|
+
'Set CLERK_JWT_KEY or pass jwtKey in ClerkAuthConfig.');
|
|
75
|
+
}
|
|
76
|
+
const headers = extractHeaders(context);
|
|
77
|
+
if (!headers)
|
|
78
|
+
return null;
|
|
79
|
+
const token = extractToken(headers);
|
|
80
|
+
if (!token)
|
|
81
|
+
return null;
|
|
82
|
+
try {
|
|
83
|
+
const verifyOptions = {
|
|
84
|
+
jwtKey: this.config.jwtKey,
|
|
85
|
+
};
|
|
86
|
+
if (this.config.authorizedParties)
|
|
87
|
+
verifyOptions.authorizedParties = this.config.authorizedParties;
|
|
88
|
+
const payload = await clerkVerifyToken(token, verifyOptions);
|
|
89
|
+
return payload?.sub ? { userId: payload.sub } : null;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
71
95
|
async authenticate(context) {
|
|
72
96
|
try {
|
|
73
97
|
// Extract headers from context (supports CanopyRequest and Headers)
|
package/dist/clerk-plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clerk-plugin.js","sourceRoot":"","sources":["../src/clerk-plugin.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"clerk-plugin.js","sourceRoot":"","sources":["../src/clerk-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGnF,OAAO,EAAE,cAAc,EAAoB,MAAM,gBAAgB,CAAA;AA2DjE;;;GAGG;AACH,SAAS,mBAAmB,CAAI,QAA0B;IACxD,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAA;AAC3D,CAAC;AAED;;;GAGG;AACH,SAAS,gBAAgB,CAAC,SAAwB;IAKhD,MAAM,SAAS,GAAG,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,SAAS,CAAA;IAC3D,OAAO;QACL,KAAK,EACH,SAAS,CAAC,mBAAmB,EAAE,YAAY,IAAI,SAAS,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa;QAC9F,IAAI,EAAE,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,EAAE;QACrF,SAAS,EAAE,SAAS,IAAI,SAAS;KAClC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,GAAsB;IAC/C,OAAO,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,aAAa,CAAA;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAoB,EAAiB,EAAE;IAClE,iCAAiC;IACjC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IAC/C,IAAI,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACtC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5B,CAAC;IAED,uBAAuB;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAA;QAC/C,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,KAAK,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,OAAO,eAAe;IAQ1B,YAAY,SAA0B,EAAE;QACtC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;QAClE,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,wFAAwF,CACzF,CAAA;QACH,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;QACzD,MAAM,iBAAiB,GACrB,MAAM,CAAC,iBAAiB;YACxB,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,KAAK,CAAC,GAAG,CAAC;iBAC7C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,OAAO,CAAC,CAAA;QAEpB,IAAI,CAAC,MAAM,GAAG;YACZ,wBAAwB,EAAE,MAAM,CAAC,wBAAwB,IAAI,IAAI;YACjE,SAAS;YACT,MAAM;YACN,iBAAiB;SAClB,CAAA;QAED,IAAI,CAAC,WAAW,GAAG,iBAAiB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAA;IACrD,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,OAAgB;QACpC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,4FAA4F;gBAC1F,sDAAsD,CACzD,CAAA;QACH,CAAC;QACD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QACzB,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QACvB,IAAI,CAAC;YACH,MAAM,aAAa,GAA2C;gBAC5D,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;aAC3B,CAAA;YACD,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB;gBAC/B,aAAa,CAAC,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAA;YACjE,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;YAC5D,OAAO,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAAgB;QACjC,IAAI,CAAC;YACH,oEAAoE;YACpE,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;YACvC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,2DAA2D;iBACnE,CAAA;YACH,CAAC;YAED,6BAA6B;YAC7B,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;YACnC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAA;YACnE,CAAC;YAED,mBAAmB;YACnB,MAAM,aAAa,GAA2C;gBAC5D,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;aACjC,CAAA;YAED,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACvB,aAAa,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAA;YAC3C,CAAC;YAED,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;gBAClC,aAAa,CAAC,iBAAiB,GAAG,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAA;YACjE,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;YAE5D,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;gBAC7B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAA;YAC3D,CAAC;YAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAA;YAE1B,8BAA8B;YAC9B,MAAM,SAAS,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAkB,CAAA;YAEjF,uCAAuC;YACvC,IAAI,cAAoC,CAAA;YACxC,IAAI,IAAI,CAAC,MAAM,CAAC,wBAAwB,EAAE,CAAC;gBACzC,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,6BAA6B,CAAC;oBACvE,MAAM,EAAE,SAAS,CAAC,EAAE;iBACrB,CAAC,CAA+C,CAAA;gBAEjD,MAAM,WAAW,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;gBAC7C,cAAc,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC,CAAA;YAC5D,CAAC;YAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAA;YAE5C,0DAA0D;YAC1D,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,IAAI,EAAE;oBACJ,MAAM,EAAE,SAAS,CAAC,EAAE;oBACpB,GAAG,QAAQ;oBACX,cAAc;iBACf;aACF,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB;aACxE,CAAA;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,KAAK,GAAG,EAAE;QACzC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC;gBACzD,KAAK;gBACL,KAAK;aACN,CAAC,CAAiC,CAAA;YAEnC,MAAM,KAAK,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;YAC3C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACrB,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAA;gBAClC,OAAO;oBACL,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;oBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;iBAC5B,CAAA;YACH,CAAC,CAAC,CAAA;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAE,KAAK,CAAC,CAAA;YAC3D,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,MAAc;QAClC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAkB,CAAA;YAC5E,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;YACrC,OAAO;gBACL,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,EAAE;gBACzB,SAAS,EAAE,MAAM,CAAC,SAAS;aAC5B,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAAA;YAC/D,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,OAAe;QACpC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,wBAAwB,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAA;QACb,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,eAAe,CAAC;gBAChE,cAAc,EAAE,OAAO;aACxB,CAAC,CAAsB,CAAA;YAExB,OAAO;gBACL,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,WAAW,EAAE,iBAAiB,CAAC,GAAG,CAAC;aACpC,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,KAAK,CAAC,CAAA;YAChE,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAK,GAAG,EAAE;QACzB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,wBAAwB,EAAE,CAAC;YAC1C,OAAO,EAAE,CAAA;QACX,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,mBAAmB,CAAC;gBACzE,KAAK;aACN,CAAC,CAAqC,CAAA;YAEvC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;YAC1C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtB,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;aAClC,CAAC,CAAC,CAAA;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAA;YAC1D,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAuC,CAAC,MAAM,EAAE,EAAE;IAClF,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAA;AACpC,CAAC,CAAA"}
|
package/dist/jwt-verifier.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export interface ClerkJwtVerifierConfig {
|
|
|
23
23
|
* with no internet access.
|
|
24
24
|
*
|
|
25
25
|
* Returns a TokenVerifier compatible with CachingAuthPlugin.
|
|
26
|
+
*
|
|
27
|
+
* @deprecated Use `ClerkAuthPlugin.verifyTokenOnly()` instead. The plugin's method is
|
|
28
|
+
* automatically wired into CachingAuthPlugin by `createNextCanopyContext()` in prod/prod-sim.
|
|
26
29
|
*/
|
|
27
30
|
export declare function createClerkJwtVerifier(config: ClerkJwtVerifierConfig): TokenVerifier;
|
|
28
31
|
//# sourceMappingURL=jwt-verifier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt-verifier.d.ts","sourceRoot":"","sources":["../src/jwt-verifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAED
|
|
1
|
+
{"version":3,"file":"jwt-verifier.d.ts","sourceRoot":"","sources":["../src/jwt-verifier.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,aAAa,CA8BpF"}
|
package/dist/jwt-verifier.js
CHANGED
|
@@ -9,6 +9,9 @@ import { extractToken } from './clerk-plugin';
|
|
|
9
9
|
* with no internet access.
|
|
10
10
|
*
|
|
11
11
|
* Returns a TokenVerifier compatible with CachingAuthPlugin.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated Use `ClerkAuthPlugin.verifyTokenOnly()` instead. The plugin's method is
|
|
14
|
+
* automatically wired into CachingAuthPlugin by `createNextCanopyContext()` in prod/prod-sim.
|
|
12
15
|
*/
|
|
13
16
|
export function createClerkJwtVerifier(config) {
|
|
14
17
|
return async (context) => {
|
package/dist/jwt-verifier.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt-verifier.js","sourceRoot":"","sources":["../src/jwt-verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAmB7C
|
|
1
|
+
{"version":3,"file":"jwt-verifier.js","sourceRoot":"","sources":["../src/jwt-verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAmB7C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAA8B;IACnE,OAAO,KAAK,EAAE,OAAgB,EAAE,EAAE;QAChC,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA;QAEzB,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAA;QAEvB,IAAI,CAAC;YACH,MAAM,aAAa,GAA2C,EAAE,CAAA;YAEhE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,aAAa,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;YACtC,CAAC;YACD,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACrB,aAAa,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAA;YAC5C,CAAC;YACD,IAAI,MAAM,CAAC,iBAAiB,EAAE,CAAC;gBAC7B,aAAa,CAAC,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAA;YAC5D,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;YAE5D,IAAI,CAAC,OAAO,EAAE,GAAG;gBAAE,OAAO,IAAI,CAAA;YAE9B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,CAAA;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopycms-auth-clerk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Clerk authentication provider for CanopyCMS",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"./cache-writer": "./src/cache-writer.ts"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
22
|
-
"src"
|
|
21
|
+
"dist"
|
|
23
22
|
],
|
|
24
23
|
"publishConfig": {
|
|
25
24
|
"exports": {
|
|
@@ -46,7 +45,7 @@
|
|
|
46
45
|
"node": ">=18"
|
|
47
46
|
},
|
|
48
47
|
"peerDependencies": {
|
|
49
|
-
"canopycms": "
|
|
48
|
+
"canopycms": "^0.0.2",
|
|
50
49
|
"@clerk/backend": "^2.0.0"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
package/src/cache-writer.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { createClerkClient } from '@clerk/backend'
|
|
2
|
-
import { writeAuthCacheSnapshot } from 'canopycms/auth/cache'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Response types that handle both camelCase and snake_case Clerk SDK variants.
|
|
6
|
-
*/
|
|
7
|
-
interface ClerkUserData {
|
|
8
|
-
id: string
|
|
9
|
-
username?: string
|
|
10
|
-
fullName?: string | null
|
|
11
|
-
full_name?: string | null
|
|
12
|
-
imageUrl?: string | null
|
|
13
|
-
image_url?: string | null
|
|
14
|
-
primaryEmailAddress?: { emailAddress: string } | null
|
|
15
|
-
email_addresses?: Array<{ email_address: string }> | null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ClerkOrganization {
|
|
19
|
-
id: string
|
|
20
|
-
name: string
|
|
21
|
-
membersCount?: number
|
|
22
|
-
members_count?: number
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface ClerkOrganizationMembership {
|
|
26
|
-
organization: ClerkOrganization
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface ClerkPaginatedResponse<T> {
|
|
30
|
-
data: T[]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type ClerkResponse<T> = T[] | ClerkPaginatedResponse<T>
|
|
34
|
-
|
|
35
|
-
function unwrapClerkResponse<T>(response: ClerkResponse<T>): T[] {
|
|
36
|
-
return Array.isArray(response) ? response : response.data
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface RefreshClerkCacheOptions {
|
|
40
|
-
/** Clerk Secret Key (CLERK_SECRET_KEY) */
|
|
41
|
-
secretKey: string
|
|
42
|
-
/** Directory to write cache files to (e.g., /mnt/efs/workspace/.cache) */
|
|
43
|
-
cachePath: string
|
|
44
|
-
/** Whether to treat Clerk organizations as groups (default: true) */
|
|
45
|
-
useOrganizationsAsGroups?: boolean
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface RefreshClerkCacheResult {
|
|
49
|
-
userCount: number
|
|
50
|
-
groupCount: number
|
|
51
|
-
membershipCount: number
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Fetches all user/org metadata from Clerk API and writes to JSON cache files.
|
|
56
|
-
*
|
|
57
|
-
* Used by the EC2 worker to populate the cache that FileBasedAuthCache reads.
|
|
58
|
-
* Writes atomically (write to temp file, then rename) to avoid partial reads.
|
|
59
|
-
*
|
|
60
|
-
* Output files:
|
|
61
|
-
* - {cachePath}/users.json — { users: UserSearchResult[] }
|
|
62
|
-
* - {cachePath}/orgs.json — { groups: GroupMetadata[] }
|
|
63
|
-
* - {cachePath}/memberships.json — { memberships: { [userId]: groupId[] } }
|
|
64
|
-
*/
|
|
65
|
-
export async function refreshClerkCache(
|
|
66
|
-
options: RefreshClerkCacheOptions,
|
|
67
|
-
): Promise<RefreshClerkCacheResult> {
|
|
68
|
-
const { secretKey, cachePath, useOrganizationsAsGroups = true } = options
|
|
69
|
-
|
|
70
|
-
const clerkClient = createClerkClient({ secretKey })
|
|
71
|
-
|
|
72
|
-
// Fetch all users (paginate to handle large organizations)
|
|
73
|
-
const clerkUsers: ClerkUserData[] = []
|
|
74
|
-
const pageSize = 500
|
|
75
|
-
let offset = 0
|
|
76
|
-
// eslint-disable-next-line no-constant-condition
|
|
77
|
-
while (true) {
|
|
78
|
-
const usersResponse = (await clerkClient.users.getUserList({
|
|
79
|
-
limit: pageSize,
|
|
80
|
-
offset,
|
|
81
|
-
})) as ClerkResponse<ClerkUserData>
|
|
82
|
-
const page = unwrapClerkResponse(usersResponse)
|
|
83
|
-
clerkUsers.push(...page)
|
|
84
|
-
if (page.length < pageSize) break
|
|
85
|
-
offset += pageSize
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const users = clerkUsers.map((u) => ({
|
|
89
|
-
id: u.id,
|
|
90
|
-
name: u.fullName ?? u.full_name ?? u.username ?? u.id,
|
|
91
|
-
email: u.primaryEmailAddress?.emailAddress ?? u.email_addresses?.[0]?.email_address ?? '',
|
|
92
|
-
avatarUrl: u.imageUrl ?? u.image_url ?? undefined,
|
|
93
|
-
}))
|
|
94
|
-
|
|
95
|
-
let groups: Array<{ id: string; name: string; memberCount?: number }> = []
|
|
96
|
-
const memberships: Record<string, string[]> = {}
|
|
97
|
-
|
|
98
|
-
if (useOrganizationsAsGroups) {
|
|
99
|
-
// Fetch all organizations (paginate)
|
|
100
|
-
const clerkOrgs: ClerkOrganization[] = []
|
|
101
|
-
let orgOffset = 0
|
|
102
|
-
const orgPageSize = 100
|
|
103
|
-
// eslint-disable-next-line no-constant-condition
|
|
104
|
-
while (true) {
|
|
105
|
-
const orgsResponse = (await clerkClient.organizations.getOrganizationList({
|
|
106
|
-
limit: orgPageSize,
|
|
107
|
-
offset: orgOffset,
|
|
108
|
-
})) as ClerkResponse<ClerkOrganization>
|
|
109
|
-
const page = unwrapClerkResponse(orgsResponse)
|
|
110
|
-
clerkOrgs.push(...page)
|
|
111
|
-
if (page.length < orgPageSize) break
|
|
112
|
-
orgOffset += orgPageSize
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
groups = clerkOrgs.map((o) => ({
|
|
116
|
-
id: o.id,
|
|
117
|
-
name: o.name,
|
|
118
|
-
memberCount: o.membersCount ?? o.members_count,
|
|
119
|
-
}))
|
|
120
|
-
|
|
121
|
-
// Fetch memberships per user
|
|
122
|
-
for (const user of clerkUsers) {
|
|
123
|
-
try {
|
|
124
|
-
const membershipResponse = (await clerkClient.users.getOrganizationMembershipList({
|
|
125
|
-
userId: user.id,
|
|
126
|
-
})) as ClerkResponse<ClerkOrganizationMembership>
|
|
127
|
-
const userMemberships = unwrapClerkResponse(membershipResponse)
|
|
128
|
-
if (userMemberships.length > 0) {
|
|
129
|
-
memberships[user.id] = userMemberships.map((m) => m.organization.id)
|
|
130
|
-
}
|
|
131
|
-
} catch (err) {
|
|
132
|
-
console.warn(
|
|
133
|
-
`Failed to fetch memberships for user ${user.id}:`,
|
|
134
|
-
err instanceof Error ? err.message : err,
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Write cache files atomically via snapshot directory + symlink swap
|
|
141
|
-
await writeAuthCacheSnapshot(cachePath, {
|
|
142
|
-
'users.json': { users },
|
|
143
|
-
'orgs.json': { groups },
|
|
144
|
-
'memberships.json': { memberships },
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
userCount: users.length,
|
|
149
|
-
groupCount: groups.length,
|
|
150
|
-
membershipCount: Object.keys(memberships).length,
|
|
151
|
-
}
|
|
152
|
-
}
|
package/src/clerk-plugin.test.ts
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
-
import { mockConsole } from '../../canopycms/src/test-utils/console-spy.js'
|
|
3
|
-
|
|
4
|
-
// Create mock objects
|
|
5
|
-
const mockGetUser = vi.fn()
|
|
6
|
-
const mockGetUserList = vi.fn()
|
|
7
|
-
const mockGetOrganizationMembershipList = vi.fn()
|
|
8
|
-
const mockGetOrganization = vi.fn()
|
|
9
|
-
const mockGetOrganizationList = vi.fn()
|
|
10
|
-
|
|
11
|
-
const mockClerkClient = {
|
|
12
|
-
users: {
|
|
13
|
-
getUser: mockGetUser,
|
|
14
|
-
getUserList: mockGetUserList,
|
|
15
|
-
getOrganizationMembershipList: mockGetOrganizationMembershipList,
|
|
16
|
-
},
|
|
17
|
-
organizations: {
|
|
18
|
-
getOrganization: mockGetOrganization,
|
|
19
|
-
getOrganizationList: mockGetOrganizationList,
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Mock @clerk/backend - must be hoisted before imports
|
|
24
|
-
vi.mock('@clerk/backend', () => ({
|
|
25
|
-
createClerkClient: vi.fn(() => mockClerkClient),
|
|
26
|
-
verifyToken: vi.fn(),
|
|
27
|
-
}))
|
|
28
|
-
|
|
29
|
-
import { ClerkAuthPlugin } from './clerk-plugin'
|
|
30
|
-
import { verifyToken } from '@clerk/backend'
|
|
31
|
-
import type { CanopyRequest } from 'canopycms/http'
|
|
32
|
-
|
|
33
|
-
const mockVerifyToken = verifyToken as any
|
|
34
|
-
|
|
35
|
-
describe('ClerkAuthPlugin', () => {
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
vi.clearAllMocks()
|
|
38
|
-
// Set default env var
|
|
39
|
-
process.env.CLERK_SECRET_KEY = 'sk_test_1234'
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
describe('constructor', () => {
|
|
43
|
-
it('throws error if CLERK_SECRET_KEY not provided', () => {
|
|
44
|
-
delete process.env.CLERK_SECRET_KEY
|
|
45
|
-
expect(() => new ClerkAuthPlugin()).toThrow('CLERK_SECRET_KEY')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
it('uses env var for secret key by default', () => {
|
|
49
|
-
const plugin = new ClerkAuthPlugin()
|
|
50
|
-
expect(plugin).toBeDefined()
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('uses default config values', () => {
|
|
54
|
-
const plugin = new ClerkAuthPlugin()
|
|
55
|
-
// Config is private, but we can test behavior
|
|
56
|
-
expect(plugin).toBeDefined()
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
describe('authenticate', () => {
|
|
61
|
-
it('returns failure if no token in request', async () => {
|
|
62
|
-
const plugin = new ClerkAuthPlugin()
|
|
63
|
-
const req = {
|
|
64
|
-
method: 'GET',
|
|
65
|
-
header: vi.fn().mockReturnValue(null),
|
|
66
|
-
} as unknown as CanopyRequest
|
|
67
|
-
|
|
68
|
-
const result = await plugin.authenticate(req)
|
|
69
|
-
|
|
70
|
-
expect(result.success).toBe(false)
|
|
71
|
-
expect(result.error).toBe('No authentication token found')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('returns failure if token verification fails', async () => {
|
|
75
|
-
const plugin = new ClerkAuthPlugin()
|
|
76
|
-
const req = {
|
|
77
|
-
method: 'GET',
|
|
78
|
-
header: vi.fn().mockImplementation((name: string) => {
|
|
79
|
-
if (name === 'Authorization') return 'Bearer test_token'
|
|
80
|
-
return null
|
|
81
|
-
}),
|
|
82
|
-
} as unknown as CanopyRequest
|
|
83
|
-
|
|
84
|
-
mockVerifyToken.mockRejectedValue(new Error('Invalid token'))
|
|
85
|
-
|
|
86
|
-
const result = await plugin.authenticate(req)
|
|
87
|
-
|
|
88
|
-
expect(result.success).toBe(false)
|
|
89
|
-
expect(result.error).toBe('Invalid token')
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('verifies valid session and returns user identity', async () => {
|
|
93
|
-
const plugin = new ClerkAuthPlugin()
|
|
94
|
-
const req = {
|
|
95
|
-
method: 'GET',
|
|
96
|
-
header: vi.fn().mockImplementation((name: string) => {
|
|
97
|
-
if (name === 'Authorization') return 'Bearer valid_token'
|
|
98
|
-
return null
|
|
99
|
-
}),
|
|
100
|
-
} as unknown as CanopyRequest
|
|
101
|
-
|
|
102
|
-
mockVerifyToken.mockResolvedValue({
|
|
103
|
-
sub: 'user_123',
|
|
104
|
-
sid: 'sess_123',
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
mockGetUser.mockResolvedValue({
|
|
108
|
-
id: 'user_123',
|
|
109
|
-
fullName: 'John Doe',
|
|
110
|
-
primaryEmailAddress: { emailAddress: 'john@example.com' },
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
mockGetOrganizationMembershipList.mockResolvedValue({
|
|
114
|
-
data: [{ organization: { id: 'org_1' } }, { organization: { id: 'org_2' } }],
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const result = await plugin.authenticate(req)
|
|
118
|
-
|
|
119
|
-
expect(result.success).toBe(true)
|
|
120
|
-
expect(result.user).toEqual({
|
|
121
|
-
userId: 'user_123',
|
|
122
|
-
name: 'John Doe',
|
|
123
|
-
email: 'john@example.com',
|
|
124
|
-
externalGroups: ['org_1', 'org_2'],
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('returns user without external groups if organizations disabled', async () => {
|
|
129
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: false })
|
|
130
|
-
const req = {
|
|
131
|
-
method: 'GET',
|
|
132
|
-
header: vi.fn().mockImplementation((name: string) => {
|
|
133
|
-
if (name === 'Authorization') return 'Bearer valid_token'
|
|
134
|
-
return null
|
|
135
|
-
}),
|
|
136
|
-
} as unknown as CanopyRequest
|
|
137
|
-
|
|
138
|
-
mockVerifyToken.mockResolvedValue({
|
|
139
|
-
sub: 'user_123',
|
|
140
|
-
sid: 'sess_123',
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
mockGetUser.mockResolvedValue({
|
|
144
|
-
id: 'user_123',
|
|
145
|
-
fullName: 'Jane Doe',
|
|
146
|
-
primaryEmailAddress: { emailAddress: 'jane@example.com' },
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
const result = await plugin.authenticate(req)
|
|
150
|
-
|
|
151
|
-
expect(result.success).toBe(true)
|
|
152
|
-
expect(result.user?.externalGroups).toBeUndefined()
|
|
153
|
-
expect(mockGetOrganizationMembershipList).not.toHaveBeenCalled()
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('handles errors gracefully', async () => {
|
|
157
|
-
const plugin = new ClerkAuthPlugin()
|
|
158
|
-
const req = {
|
|
159
|
-
method: 'GET',
|
|
160
|
-
header: vi.fn().mockImplementation((name: string) => {
|
|
161
|
-
if (name === 'Authorization') return 'Bearer valid_token'
|
|
162
|
-
return null
|
|
163
|
-
}),
|
|
164
|
-
} as unknown as CanopyRequest
|
|
165
|
-
|
|
166
|
-
mockVerifyToken.mockRejectedValue(new Error('Network error'))
|
|
167
|
-
|
|
168
|
-
const result = await plugin.authenticate(req)
|
|
169
|
-
|
|
170
|
-
expect(result.success).toBe(false)
|
|
171
|
-
expect(result.error).toBe('Network error')
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('extracts token from __session cookie', async () => {
|
|
175
|
-
const plugin = new ClerkAuthPlugin()
|
|
176
|
-
const req = {
|
|
177
|
-
method: 'GET',
|
|
178
|
-
header: vi.fn().mockImplementation((name: string) => {
|
|
179
|
-
if (name === 'Cookie') return '__session=cookie_token; other=value'
|
|
180
|
-
return null
|
|
181
|
-
}),
|
|
182
|
-
} as unknown as CanopyRequest
|
|
183
|
-
|
|
184
|
-
mockVerifyToken.mockResolvedValue({
|
|
185
|
-
sub: 'user_123',
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
mockGetUser.mockResolvedValue({
|
|
189
|
-
id: 'user_123',
|
|
190
|
-
fullName: 'Cookie User',
|
|
191
|
-
primaryEmailAddress: { emailAddress: 'cookie@example.com' },
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
mockGetOrganizationMembershipList.mockResolvedValue({ data: [] })
|
|
195
|
-
|
|
196
|
-
const result = await plugin.authenticate(req)
|
|
197
|
-
|
|
198
|
-
expect(result.success).toBe(true)
|
|
199
|
-
expect(mockVerifyToken).toHaveBeenCalledWith('cookie_token', expect.any(Object))
|
|
200
|
-
})
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
describe('searchUsers', () => {
|
|
204
|
-
it('searches users and returns results', async () => {
|
|
205
|
-
const plugin = new ClerkAuthPlugin()
|
|
206
|
-
|
|
207
|
-
mockGetUserList.mockResolvedValue({
|
|
208
|
-
data: [
|
|
209
|
-
{
|
|
210
|
-
id: 'user_1',
|
|
211
|
-
fullName: 'Alice Smith',
|
|
212
|
-
primaryEmailAddress: { emailAddress: 'alice@example.com' },
|
|
213
|
-
imageUrl: 'https://example.com/alice.jpg',
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
id: 'user_2',
|
|
217
|
-
username: 'bob',
|
|
218
|
-
primaryEmailAddress: { emailAddress: 'bob@example.com' },
|
|
219
|
-
imageUrl: 'https://example.com/bob.jpg',
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
const results = await plugin.searchUsers('alice')
|
|
225
|
-
|
|
226
|
-
expect(results).toHaveLength(2)
|
|
227
|
-
expect(results[0]).toEqual({
|
|
228
|
-
id: 'user_1',
|
|
229
|
-
name: 'Alice Smith',
|
|
230
|
-
email: 'alice@example.com',
|
|
231
|
-
avatarUrl: 'https://example.com/alice.jpg',
|
|
232
|
-
})
|
|
233
|
-
expect(results[1]).toEqual({
|
|
234
|
-
id: 'user_2',
|
|
235
|
-
name: 'bob',
|
|
236
|
-
email: 'bob@example.com',
|
|
237
|
-
avatarUrl: 'https://example.com/bob.jpg',
|
|
238
|
-
})
|
|
239
|
-
expect(mockGetUserList).toHaveBeenCalledWith({
|
|
240
|
-
query: 'alice',
|
|
241
|
-
limit: 10,
|
|
242
|
-
})
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('returns empty array on error', async () => {
|
|
246
|
-
const consoleSpy = mockConsole()
|
|
247
|
-
const plugin = new ClerkAuthPlugin()
|
|
248
|
-
|
|
249
|
-
mockGetUserList.mockRejectedValue(new Error('API error'))
|
|
250
|
-
|
|
251
|
-
const results = await plugin.searchUsers('test')
|
|
252
|
-
|
|
253
|
-
expect(results).toEqual([])
|
|
254
|
-
consoleSpy.restore()
|
|
255
|
-
})
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
describe('getUserMetadata', () => {
|
|
259
|
-
it('gets user metadata by ID', async () => {
|
|
260
|
-
const plugin = new ClerkAuthPlugin()
|
|
261
|
-
|
|
262
|
-
mockGetUser.mockResolvedValue({
|
|
263
|
-
id: 'user_123',
|
|
264
|
-
fullName: 'Test User',
|
|
265
|
-
primaryEmailAddress: { emailAddress: 'test@example.com' },
|
|
266
|
-
imageUrl: 'https://example.com/test.jpg',
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
const result = await plugin.getUserMetadata('user_123')
|
|
270
|
-
|
|
271
|
-
expect(result).toEqual({
|
|
272
|
-
id: 'user_123',
|
|
273
|
-
name: 'Test User',
|
|
274
|
-
email: 'test@example.com',
|
|
275
|
-
avatarUrl: 'https://example.com/test.jpg',
|
|
276
|
-
})
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
it('returns null on error', async () => {
|
|
280
|
-
const consoleSpy = mockConsole()
|
|
281
|
-
const plugin = new ClerkAuthPlugin()
|
|
282
|
-
|
|
283
|
-
mockGetUser.mockRejectedValue(new Error('User not found'))
|
|
284
|
-
|
|
285
|
-
const result = await plugin.getUserMetadata('user_123')
|
|
286
|
-
|
|
287
|
-
expect(result).toBeNull()
|
|
288
|
-
consoleSpy.restore()
|
|
289
|
-
})
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
describe('getGroupMetadata', () => {
|
|
293
|
-
it('gets organization metadata when enabled', async () => {
|
|
294
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: true })
|
|
295
|
-
|
|
296
|
-
mockGetOrganization.mockResolvedValue({
|
|
297
|
-
id: 'org_123',
|
|
298
|
-
name: 'Test Org',
|
|
299
|
-
membersCount: 42,
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
const result = await plugin.getGroupMetadata('org_123')
|
|
303
|
-
|
|
304
|
-
expect(result).toEqual({
|
|
305
|
-
id: 'org_123',
|
|
306
|
-
name: 'Test Org',
|
|
307
|
-
memberCount: 42,
|
|
308
|
-
})
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
it('returns null when organizations disabled', async () => {
|
|
312
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: false })
|
|
313
|
-
|
|
314
|
-
const result = await plugin.getGroupMetadata('org_123')
|
|
315
|
-
|
|
316
|
-
expect(result).toBeNull()
|
|
317
|
-
expect(mockGetOrganization).not.toHaveBeenCalled()
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
it('returns null on error', async () => {
|
|
321
|
-
const consoleSpy = mockConsole()
|
|
322
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: true })
|
|
323
|
-
|
|
324
|
-
mockGetOrganization.mockRejectedValue(new Error('Org not found'))
|
|
325
|
-
|
|
326
|
-
const result = await plugin.getGroupMetadata('org_123')
|
|
327
|
-
|
|
328
|
-
expect(result).toBeNull()
|
|
329
|
-
consoleSpy.restore()
|
|
330
|
-
})
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
describe('listGroups', () => {
|
|
334
|
-
it('lists organizations when enabled', async () => {
|
|
335
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: true })
|
|
336
|
-
|
|
337
|
-
mockGetOrganizationList.mockResolvedValue({
|
|
338
|
-
data: [
|
|
339
|
-
{ id: 'org_1', name: 'Org One', membersCount: 10 },
|
|
340
|
-
{ id: 'org_2', name: 'Org Two', membersCount: 20 },
|
|
341
|
-
],
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
const results = await plugin.listGroups(50)
|
|
345
|
-
|
|
346
|
-
expect(results).toHaveLength(2)
|
|
347
|
-
expect(results[0]).toEqual({
|
|
348
|
-
id: 'org_1',
|
|
349
|
-
name: 'Org One',
|
|
350
|
-
memberCount: 10,
|
|
351
|
-
})
|
|
352
|
-
expect(mockGetOrganizationList).toHaveBeenCalledWith({ limit: 50 })
|
|
353
|
-
})
|
|
354
|
-
|
|
355
|
-
it('returns empty array when organizations disabled', async () => {
|
|
356
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: false })
|
|
357
|
-
|
|
358
|
-
const results = await plugin.listGroups()
|
|
359
|
-
|
|
360
|
-
expect(results).toEqual([])
|
|
361
|
-
expect(mockGetOrganizationList).not.toHaveBeenCalled()
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
it('returns empty array on error', async () => {
|
|
365
|
-
const consoleSpy = mockConsole()
|
|
366
|
-
const plugin = new ClerkAuthPlugin({ useOrganizationsAsGroups: true })
|
|
367
|
-
|
|
368
|
-
mockGetOrganizationList.mockRejectedValue(new Error('API error'))
|
|
369
|
-
|
|
370
|
-
const results = await plugin.listGroups()
|
|
371
|
-
|
|
372
|
-
expect(results).toEqual([])
|
|
373
|
-
consoleSpy.restore()
|
|
374
|
-
})
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
describe('createClerkAuthPlugin factory', () => {
|
|
378
|
-
it('creates plugin instance', async () => {
|
|
379
|
-
const { createClerkAuthPlugin } = await import('./clerk-plugin')
|
|
380
|
-
const plugin = createClerkAuthPlugin({})
|
|
381
|
-
|
|
382
|
-
expect(plugin).toBeInstanceOf(ClerkAuthPlugin)
|
|
383
|
-
})
|
|
384
|
-
})
|
|
385
|
-
})
|
package/src/clerk-plugin.ts
DELETED
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
import { createClerkClient, verifyToken as clerkVerifyToken } from '@clerk/backend'
|
|
2
|
-
import type { AuthPlugin, AuthPluginFactory } from 'canopycms/auth'
|
|
3
|
-
import type { UserSearchResult, GroupMetadata, AuthenticationResult } from 'canopycms/auth'
|
|
4
|
-
import { extractHeaders, type HeadersLike } from 'canopycms/auth'
|
|
5
|
-
|
|
6
|
-
export interface ClerkAuthConfig {
|
|
7
|
-
/**
|
|
8
|
-
* Use organizations as groups
|
|
9
|
-
* @default true
|
|
10
|
-
*/
|
|
11
|
-
useOrganizationsAsGroups?: boolean
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Clerk Secret Key. If not provided, will use CLERK_SECRET_KEY env var.
|
|
15
|
-
*/
|
|
16
|
-
secretKey?: string
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* PEM public key for networkless JWT verification.
|
|
20
|
-
* If not provided, will use CLERK_JWT_KEY env var.
|
|
21
|
-
*/
|
|
22
|
-
jwtKey?: string
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* List of authorized parties (domains) for CSRF protection.
|
|
26
|
-
* If not provided, will parse from CLERK_AUTHORIZED_PARTIES env var.
|
|
27
|
-
*/
|
|
28
|
-
authorizedParties?: string[]
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Clerk API Response Types
|
|
32
|
-
// These handle both camelCase and snake_case variants from different Clerk SDK versions
|
|
33
|
-
|
|
34
|
-
interface ClerkUserData {
|
|
35
|
-
id: string
|
|
36
|
-
username?: string
|
|
37
|
-
fullName?: string | null
|
|
38
|
-
full_name?: string | null
|
|
39
|
-
imageUrl?: string | null
|
|
40
|
-
image_url?: string | null
|
|
41
|
-
primaryEmailAddress?: { emailAddress: string } | null
|
|
42
|
-
email_addresses?: Array<{ email_address: string }> | null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ClerkOrganization {
|
|
46
|
-
id: string
|
|
47
|
-
name: string
|
|
48
|
-
membersCount?: number
|
|
49
|
-
members_count?: number
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface ClerkOrganizationMembership {
|
|
53
|
-
organization: ClerkOrganization
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface ClerkPaginatedResponse<T> {
|
|
57
|
-
data: T[]
|
|
58
|
-
totalCount?: number
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type ClerkResponse<T> = T[] | ClerkPaginatedResponse<T>
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Unwrap Clerk paginated response to array.
|
|
65
|
-
* Clerk SDK sometimes returns arrays directly, sometimes paginated objects.
|
|
66
|
-
*/
|
|
67
|
-
function unwrapClerkResponse<T>(response: ClerkResponse<T>): T[] {
|
|
68
|
-
return Array.isArray(response) ? response : response.data
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Map Clerk user data to Canopy user metadata.
|
|
73
|
-
* Handles both camelCase and snake_case property variants.
|
|
74
|
-
*/
|
|
75
|
-
function mapClerkUserData(clerkUser: ClerkUserData): {
|
|
76
|
-
email?: string
|
|
77
|
-
name: string
|
|
78
|
-
avatarUrl?: string
|
|
79
|
-
} {
|
|
80
|
-
const avatarUrl = clerkUser.imageUrl ?? clerkUser.image_url
|
|
81
|
-
return {
|
|
82
|
-
email:
|
|
83
|
-
clerkUser.primaryEmailAddress?.emailAddress ?? clerkUser.email_addresses?.[0]?.email_address,
|
|
84
|
-
name: clerkUser.fullName ?? clerkUser.full_name ?? clerkUser.username ?? clerkUser.id,
|
|
85
|
-
avatarUrl: avatarUrl ?? undefined,
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Get member count from organization, handling property name variants.
|
|
91
|
-
*/
|
|
92
|
-
function getOrgMemberCount(org: ClerkOrganization): number | undefined {
|
|
93
|
-
return org.membersCount ?? org.members_count
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Extract token from headers.
|
|
98
|
-
* Looks for Bearer token in Authorization header or __session cookie.
|
|
99
|
-
*/
|
|
100
|
-
export const extractToken = (headers: HeadersLike): string | null => {
|
|
101
|
-
// Try Authorization header first
|
|
102
|
-
const authHeader = headers.get('Authorization')
|
|
103
|
-
if (authHeader?.startsWith('Bearer ')) {
|
|
104
|
-
return authHeader.slice(7)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Try __session cookie
|
|
108
|
-
const cookie = headers.get('Cookie')
|
|
109
|
-
if (cookie) {
|
|
110
|
-
const match = cookie.match(/__session=([^;]+)/)
|
|
111
|
-
if (match) {
|
|
112
|
-
return match[1]
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return null
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Clerk authentication plugin implementation for CanopyCMS.
|
|
121
|
-
* Uses @clerk/backend for framework-agnostic JWT verification.
|
|
122
|
-
*/
|
|
123
|
-
export class ClerkAuthPlugin implements AuthPlugin {
|
|
124
|
-
private config: Required<Omit<ClerkAuthConfig, 'secretKey' | 'jwtKey' | 'authorizedParties'>> & {
|
|
125
|
-
secretKey: string
|
|
126
|
-
jwtKey?: string
|
|
127
|
-
authorizedParties?: string[]
|
|
128
|
-
}
|
|
129
|
-
private clerkClient: ReturnType<typeof createClerkClient>
|
|
130
|
-
|
|
131
|
-
constructor(config: ClerkAuthConfig = {}) {
|
|
132
|
-
const secretKey = config.secretKey ?? process.env.CLERK_SECRET_KEY
|
|
133
|
-
if (!secretKey) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
'ClerkAuthPlugin: CLERK_SECRET_KEY environment variable or secretKey config is required',
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const jwtKey = config.jwtKey ?? process.env.CLERK_JWT_KEY
|
|
140
|
-
const authorizedParties =
|
|
141
|
-
config.authorizedParties ??
|
|
142
|
-
process.env.CLERK_AUTHORIZED_PARTIES?.split(',')
|
|
143
|
-
.map((s) => s.trim())
|
|
144
|
-
.filter(Boolean)
|
|
145
|
-
|
|
146
|
-
this.config = {
|
|
147
|
-
useOrganizationsAsGroups: config.useOrganizationsAsGroups ?? true,
|
|
148
|
-
secretKey,
|
|
149
|
-
jwtKey,
|
|
150
|
-
authorizedParties,
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
this.clerkClient = createClerkClient({ secretKey })
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async authenticate(context: unknown): Promise<AuthenticationResult> {
|
|
157
|
-
try {
|
|
158
|
-
// Extract headers from context (supports CanopyRequest and Headers)
|
|
159
|
-
const headers = extractHeaders(context)
|
|
160
|
-
if (!headers) {
|
|
161
|
-
return {
|
|
162
|
-
success: false,
|
|
163
|
-
error: 'Invalid context: expected CanopyRequest or Headers object',
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Extract token from headers
|
|
168
|
-
const token = extractToken(headers)
|
|
169
|
-
if (!token) {
|
|
170
|
-
return { success: false, error: 'No authentication token found' }
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Verify the token
|
|
174
|
-
const verifyOptions: Parameters<typeof clerkVerifyToken>[1] = {
|
|
175
|
-
secretKey: this.config.secretKey,
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (this.config.jwtKey) {
|
|
179
|
-
verifyOptions.jwtKey = this.config.jwtKey
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (this.config.authorizedParties) {
|
|
183
|
-
verifyOptions.authorizedParties = this.config.authorizedParties
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const payload = await clerkVerifyToken(token, verifyOptions)
|
|
187
|
-
|
|
188
|
-
if (!payload || !payload.sub) {
|
|
189
|
-
return { success: false, error: 'Invalid token payload' }
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const userId = payload.sub
|
|
193
|
-
|
|
194
|
-
// Get user details from Clerk
|
|
195
|
-
const clerkUser = (await this.clerkClient.users.getUser(userId)) as ClerkUserData
|
|
196
|
-
|
|
197
|
-
// Get organizations as external groups
|
|
198
|
-
let externalGroups: string[] | undefined
|
|
199
|
-
if (this.config.useOrganizationsAsGroups) {
|
|
200
|
-
const orgs = (await this.clerkClient.users.getOrganizationMembershipList({
|
|
201
|
-
userId: clerkUser.id,
|
|
202
|
-
})) as ClerkResponse<ClerkOrganizationMembership>
|
|
203
|
-
|
|
204
|
-
const memberships = unwrapClerkResponse(orgs)
|
|
205
|
-
externalGroups = memberships.map((m) => m.organization.id)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const userData = mapClerkUserData(clerkUser)
|
|
209
|
-
|
|
210
|
-
// Return identity only - core will apply bootstrap admins
|
|
211
|
-
return {
|
|
212
|
-
success: true,
|
|
213
|
-
user: {
|
|
214
|
-
userId: clerkUser.id,
|
|
215
|
-
...userData,
|
|
216
|
-
externalGroups,
|
|
217
|
-
},
|
|
218
|
-
}
|
|
219
|
-
} catch (error) {
|
|
220
|
-
return {
|
|
221
|
-
success: false,
|
|
222
|
-
error: error instanceof Error ? error.message : 'Authentication failed',
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async searchUsers(query: string, limit = 10): Promise<UserSearchResult[]> {
|
|
228
|
-
try {
|
|
229
|
-
const response = (await this.clerkClient.users.getUserList({
|
|
230
|
-
query,
|
|
231
|
-
limit,
|
|
232
|
-
})) as ClerkResponse<ClerkUserData>
|
|
233
|
-
|
|
234
|
-
const users = unwrapClerkResponse(response)
|
|
235
|
-
return users.map((u) => {
|
|
236
|
-
const mapped = mapClerkUserData(u)
|
|
237
|
-
return {
|
|
238
|
-
id: u.id,
|
|
239
|
-
name: mapped.name,
|
|
240
|
-
email: mapped.email ?? '',
|
|
241
|
-
avatarUrl: mapped.avatarUrl,
|
|
242
|
-
}
|
|
243
|
-
})
|
|
244
|
-
} catch (error) {
|
|
245
|
-
console.error('ClerkAuthPlugin: searchUsers failed', error)
|
|
246
|
-
return []
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async getUserMetadata(userId: string): Promise<UserSearchResult | null> {
|
|
251
|
-
try {
|
|
252
|
-
const user = (await this.clerkClient.users.getUser(userId)) as ClerkUserData
|
|
253
|
-
const mapped = mapClerkUserData(user)
|
|
254
|
-
return {
|
|
255
|
-
id: user.id,
|
|
256
|
-
name: mapped.name,
|
|
257
|
-
email: mapped.email ?? '',
|
|
258
|
-
avatarUrl: mapped.avatarUrl,
|
|
259
|
-
}
|
|
260
|
-
} catch (error) {
|
|
261
|
-
console.error('ClerkAuthPlugin: getUserMetadata failed', error)
|
|
262
|
-
return null
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async getGroupMetadata(groupId: string): Promise<GroupMetadata | null> {
|
|
267
|
-
if (!this.config.useOrganizationsAsGroups) {
|
|
268
|
-
return null
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
const org = (await this.clerkClient.organizations.getOrganization({
|
|
273
|
-
organizationId: groupId,
|
|
274
|
-
})) as ClerkOrganization
|
|
275
|
-
|
|
276
|
-
return {
|
|
277
|
-
id: org.id,
|
|
278
|
-
name: org.name,
|
|
279
|
-
memberCount: getOrgMemberCount(org),
|
|
280
|
-
}
|
|
281
|
-
} catch (error) {
|
|
282
|
-
console.error('ClerkAuthPlugin: getGroupMetadata failed', error)
|
|
283
|
-
return null
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async listGroups(limit = 50): Promise<GroupMetadata[]> {
|
|
288
|
-
if (!this.config.useOrganizationsAsGroups) {
|
|
289
|
-
return []
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
try {
|
|
293
|
-
const response = (await this.clerkClient.organizations.getOrganizationList({
|
|
294
|
-
limit,
|
|
295
|
-
})) as ClerkResponse<ClerkOrganization>
|
|
296
|
-
|
|
297
|
-
const orgs = unwrapClerkResponse(response)
|
|
298
|
-
return orgs.map((o) => ({
|
|
299
|
-
id: o.id,
|
|
300
|
-
name: o.name,
|
|
301
|
-
memberCount: getOrgMemberCount(o),
|
|
302
|
-
}))
|
|
303
|
-
} catch (error) {
|
|
304
|
-
console.error('ClerkAuthPlugin: listGroups failed', error)
|
|
305
|
-
return []
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Factory function to create a Clerk auth plugin instance
|
|
312
|
-
*/
|
|
313
|
-
export const createClerkAuthPlugin: AuthPluginFactory<ClerkAuthConfig> = (config) => {
|
|
314
|
-
return new ClerkAuthPlugin(config)
|
|
315
|
-
}
|
package/src/client.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useClerk } from '@clerk/nextjs'
|
|
4
|
-
import { UserButton } from '@clerk/nextjs'
|
|
5
|
-
import type { CanopyClientConfig } from 'canopycms/client'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Hook that provides Clerk-specific auth handlers and components for CanopyCMS editor.
|
|
9
|
-
* Use this in your edit page to integrate Clerk authentication with CanopyCMS.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```tsx
|
|
13
|
-
* import { useClerkAuthConfig } from 'canopycms-auth-clerk/client'
|
|
14
|
-
* import config from '../../canopycms.config'
|
|
15
|
-
*
|
|
16
|
-
* export default function EditPage() {
|
|
17
|
-
* const clerkAuth = useClerkAuthConfig()
|
|
18
|
-
* const editorConfig = config.client(clerkAuth)
|
|
19
|
-
* return <CanopyEditorPage config={editorConfig} />
|
|
20
|
-
* }
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export function useClerkAuthConfig(): Pick<CanopyClientConfig, 'editor'> {
|
|
24
|
-
const { signOut } = useClerk()
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
editor: {
|
|
28
|
-
AccountComponent: UserButton,
|
|
29
|
-
onLogoutClick: async () => {
|
|
30
|
-
try {
|
|
31
|
-
await signOut()
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Failed to sign out:', error)
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
}
|
|
38
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { ClerkAuthPlugin, createClerkAuthPlugin } from './clerk-plugin'
|
|
2
|
-
export type { ClerkAuthConfig } from './clerk-plugin'
|
|
3
|
-
export { createClerkJwtVerifier } from './jwt-verifier'
|
|
4
|
-
export type { ClerkJwtVerifierConfig } from './jwt-verifier'
|
|
5
|
-
export { refreshClerkCache } from './cache-writer'
|
|
6
|
-
export type { RefreshClerkCacheOptions, RefreshClerkCacheResult } from './cache-writer'
|
package/src/jwt-verifier.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { verifyToken as clerkVerifyToken } from '@clerk/backend'
|
|
2
|
-
import { extractHeaders } from 'canopycms/auth'
|
|
3
|
-
import type { TokenVerifier } from 'canopycms/auth'
|
|
4
|
-
import { extractToken } from './clerk-plugin'
|
|
5
|
-
|
|
6
|
-
export interface ClerkJwtVerifierConfig {
|
|
7
|
-
/**
|
|
8
|
-
* PEM public key for networkless JWT verification.
|
|
9
|
-
* Required for operation without internet access.
|
|
10
|
-
*/
|
|
11
|
-
jwtKey: string
|
|
12
|
-
/**
|
|
13
|
-
* Clerk Secret Key as fallback (requires internet).
|
|
14
|
-
* If not provided, only jwtKey verification is attempted.
|
|
15
|
-
*/
|
|
16
|
-
secretKey?: string
|
|
17
|
-
/**
|
|
18
|
-
* Authorized parties for CSRF protection.
|
|
19
|
-
*/
|
|
20
|
-
authorizedParties?: string[]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Creates a token verifier function that uses Clerk's JWT verification.
|
|
25
|
-
*
|
|
26
|
-
* When jwtKey (PEM public key) is provided, verification is **networkless** —
|
|
27
|
-
* no Clerk API calls are made. This is used in Lambda environments
|
|
28
|
-
* with no internet access.
|
|
29
|
-
*
|
|
30
|
-
* Returns a TokenVerifier compatible with CachingAuthPlugin.
|
|
31
|
-
*/
|
|
32
|
-
export function createClerkJwtVerifier(config: ClerkJwtVerifierConfig): TokenVerifier {
|
|
33
|
-
return async (context: unknown) => {
|
|
34
|
-
const headers = extractHeaders(context)
|
|
35
|
-
if (!headers) return null
|
|
36
|
-
|
|
37
|
-
const token = extractToken(headers)
|
|
38
|
-
if (!token) return null
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const verifyOptions: Parameters<typeof clerkVerifyToken>[1] = {}
|
|
42
|
-
|
|
43
|
-
if (config.jwtKey) {
|
|
44
|
-
verifyOptions.jwtKey = config.jwtKey
|
|
45
|
-
}
|
|
46
|
-
if (config.secretKey) {
|
|
47
|
-
verifyOptions.secretKey = config.secretKey
|
|
48
|
-
}
|
|
49
|
-
if (config.authorizedParties) {
|
|
50
|
-
verifyOptions.authorizedParties = config.authorizedParties
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const payload = await clerkVerifyToken(token, verifyOptions)
|
|
54
|
-
|
|
55
|
-
if (!payload?.sub) return null
|
|
56
|
-
|
|
57
|
-
return { userId: payload.sub }
|
|
58
|
-
} catch {
|
|
59
|
-
return null
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|