@owox/idp-owox-better-auth 0.18.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 (156) hide show
  1. package/README.md +119 -0
  2. package/dist/client/IdentityOwoxClient.d.ts +41 -0
  3. package/dist/client/IdentityOwoxClient.d.ts.map +1 -0
  4. package/dist/client/IdentityOwoxClient.js +128 -0
  5. package/dist/client/dto/authFlowDto.d.ts +27 -0
  6. package/dist/client/dto/authFlowDto.d.ts.map +1 -0
  7. package/dist/client/dto/authFlowDto.js +5 -0
  8. package/dist/client/dto/idpOwoxPayloadDto.d.ts +29 -0
  9. package/dist/client/dto/idpOwoxPayloadDto.d.ts.map +1 -0
  10. package/dist/client/dto/idpOwoxPayloadDto.js +28 -0
  11. package/dist/client/dto/index.d.ts +11 -0
  12. package/dist/client/dto/index.d.ts.map +1 -0
  13. package/dist/client/dto/index.js +10 -0
  14. package/dist/client/dto/introspectionDto.d.ts +70 -0
  15. package/dist/client/dto/introspectionDto.d.ts.map +1 -0
  16. package/dist/client/dto/introspectionDto.js +15 -0
  17. package/dist/client/dto/jwksDto.d.ts +102 -0
  18. package/dist/client/dto/jwksDto.d.ts.map +1 -0
  19. package/dist/client/dto/jwksDto.js +18 -0
  20. package/dist/client/dto/revocationDto.d.ts +11 -0
  21. package/dist/client/dto/revocationDto.d.ts.map +1 -0
  22. package/dist/client/dto/revocationDto.js +1 -0
  23. package/dist/client/dto/tokenDto.d.ts +33 -0
  24. package/dist/client/dto/tokenDto.d.ts.map +1 -0
  25. package/dist/client/dto/tokenDto.js +9 -0
  26. package/dist/client/dto/tokenType.d.ts +5 -0
  27. package/dist/client/dto/tokenType.d.ts.map +1 -0
  28. package/dist/client/dto/tokenType.js +1 -0
  29. package/dist/client/index.d.ts +6 -0
  30. package/dist/client/index.d.ts.map +1 -0
  31. package/dist/client/index.js +5 -0
  32. package/dist/config/idp-better-auth-config.d.ts +9 -0
  33. package/dist/config/idp-better-auth-config.d.ts.map +1 -0
  34. package/dist/config/idp-better-auth-config.js +101 -0
  35. package/dist/config/idp-owox-config.d.ts +195 -0
  36. package/dist/config/idp-owox-config.d.ts.map +1 -0
  37. package/dist/config/idp-owox-config.js +252 -0
  38. package/dist/config/index.d.ts +6 -0
  39. package/dist/config/index.d.ts.map +1 -0
  40. package/dist/config/index.js +5 -0
  41. package/dist/core/constants.d.ts +14 -0
  42. package/dist/core/constants.d.ts.map +1 -0
  43. package/dist/core/constants.js +13 -0
  44. package/dist/core/exceptions.d.ts +27 -0
  45. package/dist/core/exceptions.d.ts.map +1 -0
  46. package/dist/core/exceptions.js +36 -0
  47. package/dist/core/logger.d.ts +17 -0
  48. package/dist/core/logger.d.ts.map +1 -0
  49. package/dist/core/logger.js +66 -0
  50. package/dist/core/pkce.d.ts +21 -0
  51. package/dist/core/pkce.d.ts.map +1 -0
  52. package/dist/core/pkce.js +27 -0
  53. package/dist/facades/owox-token-facade.d.ts +27 -0
  54. package/dist/facades/owox-token-facade.d.ts.map +1 -0
  55. package/dist/facades/owox-token-facade.js +117 -0
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +13 -0
  59. package/dist/jwt/jwksCache.d.ts +24 -0
  60. package/dist/jwt/jwksCache.d.ts.map +1 -0
  61. package/dist/jwt/jwksCache.js +41 -0
  62. package/dist/jwt/parseToken.d.ts +15 -0
  63. package/dist/jwt/parseToken.d.ts.map +1 -0
  64. package/dist/jwt/parseToken.js +26 -0
  65. package/dist/jwt/verifyJwt.d.ts +13 -0
  66. package/dist/jwt/verifyJwt.d.ts.map +1 -0
  67. package/dist/jwt/verifyJwt.js +23 -0
  68. package/dist/mappers/client-payload-mapper.d.ts +6 -0
  69. package/dist/mappers/client-payload-mapper.d.ts.map +1 -0
  70. package/dist/mappers/client-payload-mapper.js +17 -0
  71. package/dist/mappers/user-info-payload-builder.d.ts +11 -0
  72. package/dist/mappers/user-info-payload-builder.d.ts.map +1 -0
  73. package/dist/mappers/user-info-payload-builder.js +28 -0
  74. package/dist/owox-better-auth-idp.d.ts +40 -0
  75. package/dist/owox-better-auth-idp.d.ts.map +1 -0
  76. package/dist/owox-better-auth-idp.js +239 -0
  77. package/dist/resources/templates/layouts/auth.ejs +29 -0
  78. package/dist/resources/templates/pages/sign-in.ejs +66 -0
  79. package/dist/resources/templates/pages/sign-up.ejs +65 -0
  80. package/dist/resources/templates/partials/brand-panel.ejs +24 -0
  81. package/dist/resources/templates/partials/footer.ejs +10 -0
  82. package/dist/resources/templates/partials/head.ejs +64 -0
  83. package/dist/resources/templates/partials/header.ejs +7 -0
  84. package/dist/services/auth/better-auth-session-service.d.ts +28 -0
  85. package/dist/services/auth/better-auth-session-service.d.ts.map +1 -0
  86. package/dist/services/auth/better-auth-session-service.js +121 -0
  87. package/dist/services/auth/pkce-flow-orchestrator.d.ts +33 -0
  88. package/dist/services/auth/pkce-flow-orchestrator.d.ts.map +1 -0
  89. package/dist/services/auth/pkce-flow-orchestrator.js +134 -0
  90. package/dist/services/auth/platform-auth-flow-client.d.ts +16 -0
  91. package/dist/services/auth/platform-auth-flow-client.d.ts.map +1 -0
  92. package/dist/services/auth/platform-auth-flow-client.js +32 -0
  93. package/dist/services/core/token-service.d.ts +25 -0
  94. package/dist/services/core/token-service.d.ts.map +1 -0
  95. package/dist/services/core/token-service.js +56 -0
  96. package/dist/services/core/user-context-service.d.ts +23 -0
  97. package/dist/services/core/user-context-service.d.ts.map +1 -0
  98. package/dist/services/core/user-context-service.js +54 -0
  99. package/dist/services/middleware/middleware-service.d.ts +19 -0
  100. package/dist/services/middleware/middleware-service.d.ts.map +1 -0
  101. package/dist/services/middleware/middleware-service.js +62 -0
  102. package/dist/services/middleware/request-handler-service.d.ts +18 -0
  103. package/dist/services/middleware/request-handler-service.d.ts.map +1 -0
  104. package/dist/services/middleware/request-handler-service.js +131 -0
  105. package/dist/services/rendering/page-service.d.ts +11 -0
  106. package/dist/services/rendering/page-service.d.ts.map +1 -0
  107. package/dist/services/rendering/page-service.js +26 -0
  108. package/dist/services/rendering/template-service.d.ts +17 -0
  109. package/dist/services/rendering/template-service.d.ts.map +1 -0
  110. package/dist/services/rendering/template-service.js +52 -0
  111. package/dist/social/google-provider.d.ts +35 -0
  112. package/dist/social/google-provider.d.ts.map +1 -0
  113. package/dist/social/google-provider.js +55 -0
  114. package/dist/social/social-provider.d.ts +23 -0
  115. package/dist/social/social-provider.d.ts.map +1 -0
  116. package/dist/social/social-provider.js +1 -0
  117. package/dist/store/database-store-factory.d.ts +8 -0
  118. package/dist/store/database-store-factory.d.ts.map +1 -0
  119. package/dist/store/database-store-factory.js +38 -0
  120. package/dist/store/database-store.d.ts +20 -0
  121. package/dist/store/database-store.d.ts.map +1 -0
  122. package/dist/store/database-store.js +1 -0
  123. package/dist/store/mysql-database-store.d.ts +40 -0
  124. package/dist/store/mysql-database-store.d.ts.map +1 -0
  125. package/dist/store/mysql-database-store.js +213 -0
  126. package/dist/store/sqlite-database-store.d.ts +32 -0
  127. package/dist/store/sqlite-database-store.d.ts.map +1 -0
  128. package/dist/store/sqlite-database-store.js +205 -0
  129. package/dist/store/store-result.d.ts +16 -0
  130. package/dist/store/store-result.d.ts.map +1 -0
  131. package/dist/store/store-result.js +25 -0
  132. package/dist/types/auth-request-context.d.ts +15 -0
  133. package/dist/types/auth-request-context.d.ts.map +1 -0
  134. package/dist/types/auth-request-context.js +12 -0
  135. package/dist/types/auth-session.d.ts +25 -0
  136. package/dist/types/auth-session.d.ts.map +1 -0
  137. package/dist/types/auth-session.js +1 -0
  138. package/dist/types/database-models.d.ts +39 -0
  139. package/dist/types/database-models.d.ts.map +1 -0
  140. package/dist/types/database-models.js +1 -0
  141. package/dist/types/index.d.ts +45 -0
  142. package/dist/types/index.d.ts.map +1 -0
  143. package/dist/types/index.js +2 -0
  144. package/dist/utils/cookie-policy.d.ts +16 -0
  145. package/dist/utils/cookie-policy.d.ts.map +1 -0
  146. package/dist/utils/cookie-policy.js +27 -0
  147. package/dist/utils/platform-redirect-builder.d.ts +29 -0
  148. package/dist/utils/platform-redirect-builder.d.ts.map +1 -0
  149. package/dist/utils/platform-redirect-builder.js +86 -0
  150. package/dist/utils/request-utils.d.ts +87 -0
  151. package/dist/utils/request-utils.d.ts.map +1 -0
  152. package/dist/utils/request-utils.js +171 -0
  153. package/dist/utils/string-utils.d.ts +13 -0
  154. package/dist/utils/string-utils.d.ts.map +1 -0
  155. package/dist/utils/string-utils.js +21 -0
  156. package/package.json +71 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public entry point for idp-owox-better-auth exports.
3
+ */
4
+ export { createBetterAuthConfig } from './config/idp-better-auth-config.js';
5
+ export { loadBetterAuthProviderConfigFromEnv, loadIdpOwoxConfigFromEnv, type BetterAuthProviderConfig, } from './config/index.js';
6
+ export { OwoxBetterAuthIdp, OwoxBetterAuthIdp as OwoxBetterAuthProvider, OwoxBetterAuthIdp as OwoxIdp, } from './owox-better-auth-idp.js';
7
+ export { BetterAuthSessionService } from './services/auth/better-auth-session-service.js';
8
+ export { MiddlewareService } from './services/middleware/middleware-service.js';
9
+ export { RequestHandlerService } from './services/middleware/request-handler-service.js';
10
+ export { PageService } from './services/rendering/page-service.js';
11
+ export { TemplateService } from './services/rendering/template-service.js';
12
+ export type { BetterAuthConfig, DatabaseConfig, MySqlConfig, SqliteConfig } from './types/index.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,OAAO,EACL,mCAAmC,EACnC,wBAAwB,EACxB,KAAK,wBAAwB,GAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,sBAAsB,EAC3C,iBAAiB,IAAI,OAAO,GAC7B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,gDAAgD,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,kDAAkD,CAAC;AACzF,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,0CAA0C,CAAC;AAG3E,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Public entry point for idp-owox-better-auth exports.
3
+ */
4
+ // Main Auth exports
5
+ export { createBetterAuthConfig } from './config/idp-better-auth-config.js';
6
+ export { loadBetterAuthProviderConfigFromEnv, loadIdpOwoxConfigFromEnv, } from './config/index.js';
7
+ export { OwoxBetterAuthIdp, OwoxBetterAuthIdp as OwoxBetterAuthProvider, OwoxBetterAuthIdp as OwoxIdp, } from './owox-better-auth-idp.js';
8
+ // Services
9
+ export { BetterAuthSessionService } from './services/auth/better-auth-session-service.js';
10
+ export { MiddlewareService } from './services/middleware/middleware-service.js';
11
+ export { RequestHandlerService } from './services/middleware/request-handler-service.js';
12
+ export { PageService } from './services/rendering/page-service.js';
13
+ export { TemplateService } from './services/rendering/template-service.js';
@@ -0,0 +1,24 @@
1
+ import { createLocalJWKSet, JWK } from 'jose';
2
+ /** JWKS payload structure. */
3
+ export interface JWKSet {
4
+ keys: JWK[];
5
+ }
6
+ /** Async loader for JWKS keys. */
7
+ export interface JwksFetcher {
8
+ (): Promise<JWKSet>;
9
+ }
10
+ interface CacheEntry {
11
+ jwks: JWKSet;
12
+ keyResolver: ReturnType<typeof createLocalJWKSet>;
13
+ exp: number;
14
+ inflight?: Promise<CacheEntry>;
15
+ }
16
+ /**
17
+ * Creates an in-memory JWKS cache with refresh support.
18
+ */
19
+ export declare function makeJwksCache(fetchJwks: JwksFetcher, key: string): {
20
+ get: (ttlMs: number) => Promise<CacheEntry>;
21
+ refresh: (ttlMs: number) => Promise<CacheEntry>;
22
+ };
23
+ export {};
24
+ //# sourceMappingURL=jwksCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwksCache.d.ts","sourceRoot":"","sources":["../../src/jwt/jwksCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAE9C,8BAA8B;AAC9B,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAED,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACrB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM;iBA2BrC,MAAM,KAAG,OAAO,CAAC,UAAU,CAAC;qBAMxB,MAAM;EAKrC"}
@@ -0,0 +1,41 @@
1
+ import { createLocalJWKSet } from 'jose';
2
+ /**
3
+ * Creates an in-memory JWKS cache with refresh support.
4
+ */
5
+ export function makeJwksCache(fetchJwks, key) {
6
+ const cache = new Map();
7
+ async function load(ttlMs) {
8
+ const existing = cache.get(key);
9
+ if (existing?.inflight)
10
+ return existing.inflight;
11
+ const inflight = (async () => {
12
+ const jwks = await fetchJwks();
13
+ const cacheEntry = {
14
+ jwks,
15
+ keyResolver: createLocalJWKSet(jwks),
16
+ exp: Date.now() + ttlMs,
17
+ };
18
+ cache.set(key, cacheEntry);
19
+ return cacheEntry;
20
+ })();
21
+ cache.set(key, { ...(existing ?? {}), inflight });
22
+ try {
23
+ return await inflight;
24
+ }
25
+ finally {
26
+ const cacheEntry = cache.get(key);
27
+ if (cacheEntry)
28
+ cacheEntry.inflight = undefined;
29
+ }
30
+ }
31
+ async function get(ttlMs) {
32
+ const cacheEntry = cache.get(key);
33
+ if (!cacheEntry || cacheEntry.exp <= Date.now())
34
+ return load(ttlMs);
35
+ return cacheEntry;
36
+ }
37
+ async function refresh(ttlMs) {
38
+ return load(ttlMs);
39
+ }
40
+ return { get, refresh };
41
+ }
@@ -0,0 +1,15 @@
1
+ import { Payload } from '@owox/idp-protocol';
2
+ import ms from 'ms';
3
+ import { IdentityOwoxClient } from '../client/index.js';
4
+ /** Configuration for JWT parsing and verification. */
5
+ export interface ParseTokenConfig {
6
+ jwtKeyCacheTtl: ms.StringValue;
7
+ clockTolerance: string | number;
8
+ expectedIss: string;
9
+ algorithm: string;
10
+ }
11
+ /**
12
+ * Parses and verifies a JWT using JWKS from Identity OWOX.
13
+ */
14
+ export declare function parseToken(token: string, client: IdentityOwoxClient, config: ParseTokenConfig): Promise<Payload>;
15
+ //# sourceMappingURL=parseToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseToken.d.ts","sourceRoot":"","sources":["../../src/jwt/parseToken.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,EAAE,CAAC,WAAW,CAAC;IAC/B,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
@@ -0,0 +1,26 @@
1
+ import { decodeProtectedHeader } from 'jose';
2
+ import { makeJwksCache } from './jwksCache.js';
3
+ import { verify } from './verifyJwt.js';
4
+ import ms from 'ms';
5
+ import { toPayload } from '../mappers/client-payload-mapper.js';
6
+ /**
7
+ * Parses and verifies a JWT using JWKS from Identity OWOX.
8
+ */
9
+ export async function parseToken(token, client, config) {
10
+ const { alg } = decodeProtectedHeader(token);
11
+ if (alg && alg !== config.algorithm) {
12
+ throw new Error(`Unsupported JWT alg: ${alg}`);
13
+ }
14
+ const fetchJwks = async () => {
15
+ const resp = await client.getJwks();
16
+ return { keys: resp.keys };
17
+ };
18
+ const cacheKey = 'JWKS_KEYS';
19
+ const cache = makeJwksCache(fetchJwks, cacheKey);
20
+ const { payload } = await verify(token, async () => (await cache.get(ms(config.jwtKeyCacheTtl))).keyResolver, async () => (await cache.refresh(ms(config.jwtKeyCacheTtl))).keyResolver, {
21
+ algorithm: config.algorithm,
22
+ clockTolerance: config.clockTolerance,
23
+ issuer: config.expectedIss,
24
+ });
25
+ return toPayload(payload);
26
+ }
@@ -0,0 +1,13 @@
1
+ import { createLocalJWKSet } from 'jose';
2
+ /** Configuration for JWT verification. */
3
+ interface verifyConfig {
4
+ algorithm: string;
5
+ clockTolerance: string | number;
6
+ issuer: string;
7
+ }
8
+ /**
9
+ * Verifies a JWT with optional JWKS refresh on key mismatch.
10
+ */
11
+ export declare function verify(token: string, getKeyResolver: () => Promise<ReturnType<typeof createLocalJWKSet>>, refreshKeyResolver: () => Promise<ReturnType<typeof createLocalJWKSet>>, config: verifyConfig): Promise<import("jose").JWTVerifyResult<import("jose").JWTPayload> & import("jose").ResolvedKey>;
12
+ export {};
13
+ //# sourceMappingURL=verifyJwt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifyJwt.d.ts","sourceRoot":"","sources":["../../src/jwt/verifyJwt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAIpD,0CAA0C;AAC1C,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,EACnE,kBAAkB,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,EACvE,MAAM,EAAE,YAAY,mGAmBrB"}
@@ -0,0 +1,23 @@
1
+ import { jwtVerify } from 'jose';
2
+ import { JWKSNoMatchingKey, JWSSignatureVerificationFailed } from 'jose/errors';
3
+ /**
4
+ * Verifies a JWT with optional JWKS refresh on key mismatch.
5
+ */
6
+ export async function verify(token, getKeyResolver, refreshKeyResolver, config) {
7
+ const jwtVerifyOptions = {
8
+ algorithms: [config.algorithm],
9
+ clockTolerance: config.clockTolerance,
10
+ issuer: config.issuer,
11
+ };
12
+ try {
13
+ const key = await getKeyResolver();
14
+ return await jwtVerify(token, key, jwtVerifyOptions);
15
+ }
16
+ catch (err) {
17
+ const shouldRefreshKey = err instanceof JWKSNoMatchingKey || err instanceof JWSSignatureVerificationFailed;
18
+ if (!shouldRefreshKey)
19
+ throw err;
20
+ const keyAfterReload = await refreshKeyResolver();
21
+ return await jwtVerify(token, keyAfterReload, jwtVerifyOptions);
22
+ }
23
+ }
@@ -0,0 +1,6 @@
1
+ import { Payload } from '@owox/idp-protocol';
2
+ /**
3
+ * Maps Identity OWOX payloads into idp-protocol payloads.
4
+ */
5
+ export declare function toPayload(input: unknown): Payload;
6
+ //# sourceMappingURL=client-payload-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-payload-mapper.d.ts","sourceRoot":"","sources":["../../src/mappers/client-payload-mapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAiB,MAAM,oBAAoB,CAAC;AAY5D;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEjD"}
@@ -0,0 +1,17 @@
1
+ import { IdpOwoxPayloadSchema } from '../client/index.js';
2
+ import { PayloadSchema } from '@owox/idp-protocol';
3
+ const IdpOwoxToPayloadSchema = IdpOwoxPayloadSchema.transform((src) => ({
4
+ userId: src.userId,
5
+ projectId: src.projectId,
6
+ email: src.userEmail,
7
+ fullName: src.userFullName,
8
+ avatar: src.userAvatar ?? undefined,
9
+ roles: src.roles,
10
+ projectTitle: src.projectTitle,
11
+ })).pipe(PayloadSchema);
12
+ /**
13
+ * Maps Identity OWOX payloads into idp-protocol payloads.
14
+ */
15
+ export function toPayload(input) {
16
+ return IdpOwoxToPayloadSchema.parse(input);
17
+ }
@@ -0,0 +1,11 @@
1
+ import type { UserInfoPayload } from '../services/auth/platform-auth-flow-client.js';
2
+ import type { DatabaseAccount, DatabaseUser } from '../types/database-models.js';
3
+ /**
4
+ * Builds the auth-flow payload from DB user and account data.
5
+ */
6
+ export declare function buildUserInfoPayload(params: {
7
+ state: string;
8
+ user: DatabaseUser;
9
+ account: DatabaseAccount;
10
+ }): UserInfoPayload;
11
+ //# sourceMappingURL=user-info-payload-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-info-payload-builder.d.ts","sourceRoot":"","sources":["../../src/mappers/user-info-payload-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+CAA+C,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAGjF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,eAAe,CAAC;CAC1B,GAAG,eAAe,CAqBlB"}
@@ -0,0 +1,28 @@
1
+ import { splitName } from '../utils/string-utils.js';
2
+ /**
3
+ * Builds the auth-flow payload from DB user and account data.
4
+ */
5
+ export function buildUserInfoPayload(params) {
6
+ const email = params.user.email;
7
+ if (!email) {
8
+ throw new Error('Email not found in DB');
9
+ }
10
+ const { firstName, lastName, fullName } = splitName(params.user.name);
11
+ const avatar = params.user.image ?? undefined;
12
+ return {
13
+ state: params.state,
14
+ userInfo: {
15
+ uid: params.account.accountId,
16
+ signinProvider: params.account.providerId,
17
+ email,
18
+ firstName: toOptional(firstName),
19
+ lastName: toOptional(lastName),
20
+ fullName: toOptional(fullName),
21
+ avatar,
22
+ },
23
+ };
24
+ }
25
+ function toOptional(value) {
26
+ const trimmed = value?.trim();
27
+ return trimmed ? trimmed : undefined;
28
+ }
@@ -0,0 +1,40 @@
1
+ import { AuthResult, IdpProvider, Payload, Projects } from '@owox/idp-protocol';
2
+ import e, { Express, NextFunction } from 'express';
3
+ import type { BetterAuthProviderConfig } from './config/index.js';
4
+ /**
5
+ * Main IdP implementation that wires core PKCE flow and Better Auth.
6
+ */
7
+ export declare class OwoxBetterAuthIdp implements IdpProvider {
8
+ private readonly config;
9
+ private readonly auth;
10
+ private readonly store;
11
+ private readonly requestHandlerService;
12
+ private readonly pageService;
13
+ private readonly betterAuthSessionService;
14
+ private readonly middlewareService;
15
+ private readonly identityClient;
16
+ private readonly logger;
17
+ private readonly tokenFacade;
18
+ private readonly userContextService;
19
+ private readonly platformAuthFlowClient;
20
+ private readonly pkceFlowOrchestrator;
21
+ private constructor();
22
+ static create(config: BetterAuthProviderConfig): Promise<OwoxBetterAuthIdp>;
23
+ initialize(): Promise<void>;
24
+ registerRoutes(app: Express): void;
25
+ signInMiddleware(req: e.Request, res: e.Response, next: NextFunction): Promise<void | e.Response>;
26
+ signUpMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<void | e.Response>;
27
+ signOutMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<void | e.Response>;
28
+ userApiMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<e.Response<Payload>>;
29
+ projectsApiMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<e.Response<Projects>>;
30
+ introspectToken(token: string): Promise<Payload | null>;
31
+ parseToken(token: string): Promise<Payload | null>;
32
+ verifyToken(token: string): Promise<Payload | null>;
33
+ refreshToken(refreshToken: string): Promise<AuthResult>;
34
+ revokeToken(token: string): Promise<void>;
35
+ accessTokenMiddleware(req: e.Request, res: e.Response, _next: NextFunction): Promise<void | e.Response>;
36
+ shutdown(): Promise<void>;
37
+ isHealthy(): Promise<boolean>;
38
+ private redirectToPlatform;
39
+ }
40
+ //# sourceMappingURL=owox-better-auth-idp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owox-better-auth-idp.d.ts","sourceRoot":"","sources":["../src/owox-better-auth-idp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,239 @@
1
+ import { ProtocolRoute } from '@owox/idp-protocol';
2
+ import { LoggerFactory } from '@owox/internal-helpers';
3
+ import cookieParser from 'cookie-parser';
4
+ import e from 'express';
5
+ import { IdentityOwoxClient } from './client/index.js';
6
+ import { createBetterAuthConfig } from './config/idp-better-auth-config.js';
7
+ import { CORE_REFRESH_TOKEN_COOKIE, SOURCE } from './core/constants.js';
8
+ import { AuthenticationException, IdpFailedException } from './core/exceptions.js';
9
+ import { OwoxTokenFacade } from './facades/owox-token-facade.js';
10
+ import { BetterAuthSessionService } from './services/auth/better-auth-session-service.js';
11
+ import { PkceFlowOrchestrator } from './services/auth/pkce-flow-orchestrator.js';
12
+ import { PlatformAuthFlowClient } from './services/auth/platform-auth-flow-client.js';
13
+ import { UserContextService } from './services/core/user-context-service.js';
14
+ import { MiddlewareService } from './services/middleware/middleware-service.js';
15
+ import { RequestHandlerService } from './services/middleware/request-handler-service.js';
16
+ import { PageService } from './services/rendering/page-service.js';
17
+ import { createDatabaseStore } from './store/database-store-factory.js';
18
+ import { clearCookie } from './utils/cookie-policy.js';
19
+ import { buildPlatformEntryUrl } from './utils/platform-redirect-builder.js';
20
+ import { clearBetterAuthCookies, clearPlatformCookies, extractPlatformParams, extractRefreshToken, getStateManager, } from './utils/request-utils.js';
21
+ import { formatError } from './utils/string-utils.js';
22
+ /**
23
+ * Main IdP implementation that wires core PKCE flow and Better Auth.
24
+ */
25
+ export class OwoxBetterAuthIdp {
26
+ config;
27
+ auth;
28
+ store;
29
+ requestHandlerService;
30
+ pageService;
31
+ betterAuthSessionService;
32
+ middlewareService;
33
+ identityClient;
34
+ logger;
35
+ tokenFacade;
36
+ userContextService;
37
+ platformAuthFlowClient;
38
+ pkceFlowOrchestrator;
39
+ constructor(auth, store, config) {
40
+ this.config = config;
41
+ this.auth = auth;
42
+ this.store = store;
43
+ this.identityClient = new IdentityOwoxClient(config.idpOwox.identityOwoxClientConfig);
44
+ this.logger = LoggerFactory.createNamedLogger('OwoxBetterAuthIdp');
45
+ this.tokenFacade = new OwoxTokenFacade(this.identityClient, this.store, this.config.idpOwox, this.logger, CORE_REFRESH_TOKEN_COOKIE);
46
+ this.userContextService = new UserContextService(this.store, this.tokenFacade, this.logger);
47
+ this.platformAuthFlowClient = new PlatformAuthFlowClient(this.identityClient);
48
+ this.betterAuthSessionService = new BetterAuthSessionService(this.auth, this.store, this.platformAuthFlowClient);
49
+ this.pkceFlowOrchestrator = new PkceFlowOrchestrator(this.config.idpOwox, this.tokenFacade, this.userContextService, this.platformAuthFlowClient, this.betterAuthSessionService, this.logger);
50
+ this.requestHandlerService = new RequestHandlerService(this.auth, this.pkceFlowOrchestrator);
51
+ this.pageService = new PageService();
52
+ this.middlewareService = new MiddlewareService(this.pageService, this.config.idpOwox, this.store, this.pkceFlowOrchestrator);
53
+ }
54
+ static async create(config) {
55
+ const store = createDatabaseStore(config.idpOwox.dbConfig);
56
+ const adapter = await store.getAdapter();
57
+ const auth = await createBetterAuthConfig(config.betterAuth, {
58
+ adapter,
59
+ });
60
+ return new OwoxBetterAuthIdp(auth, store, config);
61
+ }
62
+ async initialize() {
63
+ const { getMigrations } = await import('better-auth/db');
64
+ const { runMigrations } = await getMigrations(this.auth.options);
65
+ await this.store.initialize();
66
+ await runMigrations();
67
+ }
68
+ registerRoutes(app) {
69
+ app.use(e.json());
70
+ app.use(e.urlencoded({ extended: true }));
71
+ app.use(cookieParser());
72
+ this.requestHandlerService.setupBetterAuthHandler(app);
73
+ this.pageService.registerRoutes(app);
74
+ app.get('/auth/idp-start', this.middlewareService.idpStartMiddleware.bind(this.middlewareService));
75
+ // Core callback route (PKCE code exchange)
76
+ app.get('/auth/callback', async (req, res) => {
77
+ const code = req.query.code;
78
+ const state = req.query.state;
79
+ if (!code) {
80
+ this.logger.warn('Redirect url should contain code param');
81
+ return res.redirect(`/auth${ProtocolRoute.SIGN_IN}`);
82
+ }
83
+ if (!state) {
84
+ this.logger.warn('Redirect url should contain state param');
85
+ clearPlatformCookies(res, req);
86
+ return res.redirect(`/auth${ProtocolRoute.SIGN_IN}`);
87
+ }
88
+ try {
89
+ const response = await this.tokenFacade.changeAuthCode(code, state);
90
+ this.tokenFacade.setTokenToCookie(res, req, response.refreshToken, response.refreshTokenExpiresIn);
91
+ res.redirect('/');
92
+ }
93
+ catch (error) {
94
+ if (error instanceof AuthenticationException) {
95
+ this.logger.info(formatError(error), {
96
+ context: error.name,
97
+ params: error.context,
98
+ cause: error.cause,
99
+ });
100
+ }
101
+ else if (error instanceof IdpFailedException) {
102
+ this.logger.error('Token Exchange callback failed with unexpected code', error.context, error.cause);
103
+ }
104
+ else {
105
+ this.logger.error(formatError(error));
106
+ }
107
+ return res.redirect(`/auth${ProtocolRoute.SIGN_IN}`);
108
+ }
109
+ });
110
+ }
111
+ async signInMiddleware(req, res, next) {
112
+ const stateManager = getStateManager(req);
113
+ const queryState = typeof req.query?.state === 'string' ? req.query.state : '';
114
+ const projectId = typeof req.query?.projectId === 'string' ? req.query.projectId : '';
115
+ const refreshToken = extractRefreshToken(req);
116
+ if (stateManager.hasMismatch()) {
117
+ this.logger.warn('State mismatch detected during sign-in');
118
+ clearPlatformCookies(res, req);
119
+ return this.redirectToPlatform(req, res, this.config.idpOwox.idpConfig.platformSignInUrl);
120
+ }
121
+ if (!queryState) {
122
+ if (projectId && refreshToken) {
123
+ return this.middlewareService.idpStartMiddleware(req, res);
124
+ }
125
+ if (refreshToken) {
126
+ try {
127
+ const auth = await this.tokenFacade.refreshToken(refreshToken);
128
+ if (auth.refreshToken && auth.refreshTokenExpiresIn !== undefined) {
129
+ this.tokenFacade.setTokenToCookie(res, req, auth.refreshToken, auth.refreshTokenExpiresIn);
130
+ }
131
+ return res.redirect('/');
132
+ }
133
+ catch (error) {
134
+ if (error instanceof AuthenticationException) {
135
+ clearCookie(res, CORE_REFRESH_TOKEN_COOKIE, req);
136
+ this.logger.warn('Refresh token rejected during sign-in, cookie cleared', {
137
+ context: error.context,
138
+ cause: error.cause,
139
+ });
140
+ }
141
+ else if (error instanceof IdpFailedException) {
142
+ this.logger.warn('Sign-in refresh failed due to upstream IdP error', {
143
+ context: error.context,
144
+ cause: error.cause,
145
+ });
146
+ }
147
+ else {
148
+ this.logger.error(formatError(error));
149
+ }
150
+ }
151
+ }
152
+ return this.redirectToPlatform(req, res, this.config.idpOwox.idpConfig.platformSignInUrl);
153
+ }
154
+ stateManager.persist(res, queryState);
155
+ return this.middlewareService.signInMiddleware(req, res, next);
156
+ }
157
+ async signUpMiddleware(req, res, _next) {
158
+ const stateManager = getStateManager(req);
159
+ const queryState = typeof req.query?.state === 'string' ? req.query.state : '';
160
+ if (stateManager.hasMismatch()) {
161
+ this.logger.warn('State mismatch detected during sign-up');
162
+ clearPlatformCookies(res, req);
163
+ return this.redirectToPlatform(req, res, this.config.idpOwox.idpConfig.platformSignUpUrl);
164
+ }
165
+ if (!queryState) {
166
+ return this.redirectToPlatform(req, res, this.config.idpOwox.idpConfig.platformSignUpUrl);
167
+ }
168
+ stateManager.persist(res, queryState);
169
+ return this.middlewareService.signUpMiddleware(req, res, _next);
170
+ }
171
+ async signOutMiddleware(req, res, _next) {
172
+ const refreshToken = extractRefreshToken(req);
173
+ if (refreshToken) {
174
+ await this.revokeToken(refreshToken);
175
+ }
176
+ clearCookie(res, CORE_REFRESH_TOKEN_COOKIE, req);
177
+ clearBetterAuthCookies(res, req);
178
+ const redirectUrl = this.config.idpOwox.idpConfig.signOutRedirectUrl ?? `/auth${ProtocolRoute.SIGN_IN}`;
179
+ res.redirect(redirectUrl);
180
+ }
181
+ async userApiMiddleware(req, res, _next) {
182
+ const accessToken = req.headers['x-owox-authorization'];
183
+ if (!accessToken) {
184
+ return res.status(401).json({ message: 'Unauthorized', reason: 'uam1' });
185
+ }
186
+ const payload = await this.parseToken(accessToken);
187
+ if (!payload) {
188
+ return res.status(401).json({ message: 'Unauthorized', reason: 'uam2' });
189
+ }
190
+ return res.json(payload);
191
+ }
192
+ async projectsApiMiddleware(req, res, _next) {
193
+ const accessToken = req.headers['x-owox-authorization'];
194
+ if (!accessToken) {
195
+ return res.status(401).json({ message: 'Unauthorized', reason: 'pam1' });
196
+ }
197
+ const projects = await this.identityClient.getProjects(accessToken);
198
+ return res.json(projects);
199
+ }
200
+ async introspectToken(token) {
201
+ return this.tokenFacade.introspectToken(token);
202
+ }
203
+ async parseToken(token) {
204
+ return this.tokenFacade.parseToken(token);
205
+ }
206
+ async verifyToken(token) {
207
+ return this.tokenFacade.verifyToken(token);
208
+ }
209
+ async refreshToken(refreshToken) {
210
+ return this.tokenFacade.refreshToken(refreshToken);
211
+ }
212
+ async revokeToken(token) {
213
+ await this.tokenFacade.revokeToken(token);
214
+ }
215
+ async accessTokenMiddleware(req, res, _next) {
216
+ return this.tokenFacade.accessTokenMiddleware(req, res, _next);
217
+ }
218
+ async shutdown() {
219
+ try {
220
+ await this.store.shutdown();
221
+ }
222
+ catch (error) {
223
+ LoggerFactory.createNamedLogger('OwoxBetterAuthIdp').error('Failed to shutdown BetterAuth store', {}, error);
224
+ }
225
+ }
226
+ async isHealthy() {
227
+ return this.store.isHealthy();
228
+ }
229
+ async redirectToPlatform(req, res, authUrl) {
230
+ const params = extractPlatformParams(req);
231
+ const platformUrl = buildPlatformEntryUrl({
232
+ authUrl,
233
+ params,
234
+ defaultSource: SOURCE.APP,
235
+ allowedRedirectOrigins: this.config.idpOwox.idpConfig.allowedRedirectOrigins,
236
+ });
237
+ return res.redirect(platformUrl.toString());
238
+ }
239
+ }
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <%- include('../partials/head', { pageTitle }) %>
4
+ <body class="min-h-screen bg-background flex items-center justify-center p-4">
5
+ <!-- Split Panel Container -->
6
+ <div class="w-full max-w-5xl">
7
+ <!-- Page Header (above split panel) -->
8
+ <%- include('../partials/header', { heading }) %>
9
+
10
+ <!-- Split Panel Layout -->
11
+ <div class="mt-8 grid grid-cols-1 lg:grid-cols-2 gap-0 shadow-xl rounded-lg overflow-hidden border border-border min-h-[400px]">
12
+ <!-- Left Panel - Brand -->
13
+ <div class="hidden lg:block">
14
+ <%- include('../partials/brand-panel') %>
15
+ </div>
16
+
17
+ <!-- Right Panel - Content -->
18
+ <div class="bg-card p-8 lg:p-12 flex items-center justify-center">
19
+ <div class="w-full max-w-md">
20
+ <%- body %>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <!-- Footer -->
26
+ <%- include('../partials/footer') %>
27
+ </div>
28
+ </body>
29
+ </html>
@@ -0,0 +1,66 @@
1
+ <!-- Sign In Content -->
2
+ <div class="space-y-6">
3
+ <!-- Message -->
4
+ <p class="text-center text-sm text-muted-foreground">
5
+ Be confident that your data is protected by Google's infrastructure security standards.
6
+ </p>
7
+
8
+ <!-- Sign in button -->
9
+ <button
10
+ id="google-signin"
11
+ type="button"
12
+ class="w-full inline-flex justify-center items-center gap-2 py-2 px-4 border border-border rounded-md text-sm font-medium text-foreground bg-background hover:bg-muted transition-colors"
13
+ >
14
+ <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" x="0px" y="0px" width="100" height="100" viewBox="0 0 48 48">
15
+ <path fill="#FFC107" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12c0-6.627,5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24c0,11.045,8.955,20,20,20c11.045,0,20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"></path><path fill="#FF3D00" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"></path><path fill="#4CAF50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"></path><path fill="#1976D2" d="M43.611,20.083H42V20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"></path>
16
+ </svg>
17
+ <span>Sign in with Google</span>
18
+ </button>
19
+
20
+ <!-- Sign up link -->
21
+ <p class="text-center text-sm text-muted-foreground">
22
+ Don't have an account?
23
+ <a href="/auth/sign-up" class="font-medium text-primary hover:text-primary/80">Create one</a>
24
+ </p>
25
+ </div>
26
+
27
+ <script>
28
+ const urlParams = new URLSearchParams(window.location.search);
29
+ const redirect = urlParams.get('redirect');
30
+ const googleButton = document.getElementById('google-signin');
31
+
32
+ async function submitSocial(provider) {
33
+ const callbackURL = redirect || '/';
34
+ try {
35
+ const response = await fetch('/auth/better-auth/sign-in/social', {
36
+ method: 'POST',
37
+ headers: {
38
+ 'Content-Type': 'application/json',
39
+ },
40
+ body: JSON.stringify({ provider, callbackURL }),
41
+ redirect: 'follow',
42
+ });
43
+
44
+ if (response.redirected) {
45
+ window.location.href = response.url;
46
+ return;
47
+ }
48
+
49
+ const contentType = response.headers.get('content-type') || '';
50
+ if (contentType.includes('application/json')) {
51
+ const data = await response.json();
52
+ if (data?.url) {
53
+ window.location.href = data.url;
54
+ return;
55
+ }
56
+ }
57
+
58
+ // Fallback: reload to let server set cookies if any
59
+ window.location.reload();
60
+ } catch (error) {
61
+ console.error('Social sign-in failed', error);
62
+ }
63
+ }
64
+
65
+ googleButton?.addEventListener('click', () => submitSocial('google'));
66
+ </script>