@vritti/api-sdk 0.3.15 → 0.4.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.
package/dist/auth.d.ts CHANGED
@@ -4,7 +4,7 @@ import * as _nestjs_common from '@nestjs/common';
4
4
  import { InjectionToken, ModuleMetadata, DynamicModule, CanActivate, ExecutionContext } from '@nestjs/common';
5
5
  import { Reflector } from '@nestjs/core';
6
6
  import { JwtService, JwtSignOptions } from '@nestjs/jwt';
7
- import { FastifyRequest } from 'fastify';
7
+ import { VrittiSessionAuth } from 'fastify';
8
8
  export { W as WORKSPACE_HEADER_ORDER } from './request-CDllp7xb.js';
9
9
 
10
10
  /**
@@ -27,14 +27,6 @@ declare const PARTY_ID_HEADER = "x-party-id";
27
27
  * window, which is why app operations should stay idempotent.
28
28
  */
29
29
  declare const MAX_CLOCK_SKEW_SECONDS = 300;
30
- /**
31
- * `sessionInfo.sessionType` for an app request.
32
- *
33
- * Not a value of any server's `session_type` enum — no session row exists. It is
34
- * here so anything reading `sessionType` can tell an app apart from a person
35
- * without a second field.
36
- */
37
- declare const APP_SESSION_TYPE = "APP";
38
30
 
39
31
  interface AuthConfigInput {
40
32
  tokenExpiry: TokenExpiry;
@@ -67,42 +59,22 @@ declare const CookieName: (...dataOrPipes: unknown[]) => ParameterDecorator;
67
59
 
68
60
  declare const Hostname: (...dataOrPipes: unknown[]) => ParameterDecorator;
69
61
 
70
- declare const Public: () => _nestjs_common.CustomDecorator<string>;
71
-
72
62
  declare const RefreshCookieOptions: (...dataOrPipes: unknown[]) => ParameterDecorator;
73
63
 
74
64
  declare const RefreshTokenCookie: (...dataOrPipes: unknown[]) => ParameterDecorator;
75
65
 
76
- declare const REQUIRE_APP_KEY = "requiredAppTypes";
77
- /**
78
- * Authenticates an external app by its request signature, and restricts the
79
- * endpoint to the given app types.
80
- *
81
- * ```ts
82
- * @RequireApp(AppTypeValues.GRAPHQL)
83
- * @Mutation(() => Person, { name: 'createPerson' })
84
- * async createPerson(@Args('input') input: CreatePersonInput) {}
85
- * ```
86
- *
87
- * Metadata only, exactly like `@RequireSession` — `VrittiAuthGuard` reads it and
88
- * branches. Nothing else is needed at the call site:
89
- *
90
- * - no `@Public()`, because the guard's app branch runs *before* the public check
91
- * - no `@UseGuards()`, because the global guard owns this
92
- * - no `@SkipCsrf()`, because that branch returns before CSRF is ever reached —
93
- * which also covers REST, where a transport-level exemption would not
94
- *
95
- * Types are compared as strings: the enum belongs to the consuming server's
96
- * schema, and this only ever compares. Passing none authenticates the caller
97
- * without restricting which kind it is.
98
- *
99
- * The consuming server resolves the credential in its `onAuthenticated` callback,
100
- * which is the only side with a database. See `GuardConfig.onAuthenticated`.
101
- */
102
- declare const RequireApp: (...types: string[]) => _nestjs_common.CustomDecorator<string>;
103
-
104
- declare const REQUIRE_SESSION_KEY = "requiredSessionTypes";
105
- declare const RequireSession: (...types: string[]) => _nestjs_common.CustomDecorator<string>;
66
+ declare const REQUIRE_AUTH_KEY = "requireAuth";
67
+ declare enum AuthType {
68
+ Session = "session",
69
+ App = "app",
70
+ Cloud = "cloud",
71
+ Public = "public"
72
+ }
73
+ interface AuthRequirement {
74
+ type: AuthType;
75
+ subtypes: string[];
76
+ }
77
+ declare const Require: (type: AuthType, ...subtypes: string[]) => _nestjs_common.CustomDecorator<string>;
106
78
 
107
79
  interface SessionInfo$1 {
108
80
  userId: string;
@@ -120,7 +92,7 @@ declare const UserAgent: (...dataOrPipes: unknown[]) => ParameterDecorator;
120
92
 
121
93
  declare const UserId: (...dataOrPipes: unknown[]) => ParameterDecorator;
122
94
 
123
- type SessionInfo = NonNullable<FastifyRequest['sessionInfo']>;
95
+ type SessionInfo = Omit<VrittiSessionAuth, 'kind'>;
124
96
  declare class TokenService {
125
97
  private readonly jwtService;
126
98
  private readonly config;
@@ -150,6 +122,16 @@ declare class VrittiAuthGuard implements CanActivate {
150
122
  private readonly logger;
151
123
  constructor(reflector: Reflector, requestService: RequestService, tokenService: TokenService, config: AuthConfig);
152
124
  canActivate(context: ExecutionContext): Promise<boolean>;
125
+ /**
126
+ * Authenticates a signed request from the control plane.
127
+ *
128
+ * Same shape as the app branch and for the same reason: verifying the signature needs a key
129
+ * the consuming server holds, so this delegates to `guard.onAuthenticated` and keeps only
130
+ * what this side can do — recognising the decorator and seeding the context.
131
+ *
132
+ * There is no subtype filter. Cloud is one caller, not a family of them.
133
+ */
134
+ private handleCloudAuth;
153
135
  /**
154
136
  * Authenticates a signed request from an external app.
155
137
  *
@@ -174,4 +156,4 @@ declare class VrittiAuthGuard implements CanActivate {
174
156
  declare function hashToken(token: string): string;
175
157
  declare function verifyTokenHash(token: string, expectedHash: string): boolean;
176
158
 
177
- export { APP_SESSION_TYPE, AccessToken, AuthConfig, AuthConfigModule, CLIENT_ID_HEADER, ClientIp, CookieConfig, CookieDomain, CookieName, DecodedAccessToken, DecodedRefreshToken, GuardConfig, Hostname, MAX_CLOCK_SKEW_SECONDS, PARTY_ID_HEADER, Public, REQUIRE_APP_KEY, REQUIRE_SESSION_KEY, RefreshCookieOptions, RefreshTokenCookie, RequireApp, RequireSession, SKIP_CSRF_KEY, SessionData, type SessionInfo$1 as SessionInfo, SkipCsrf, Subdomain, TokenExpiry, TokenService, TokenType, UserAgent, UserId, VrittiAuthGuard, hashToken, verifyTokenHash };
159
+ export { AccessToken, AuthConfig, AuthConfigModule, type AuthRequirement, AuthType, CLIENT_ID_HEADER, ClientIp, CookieConfig, CookieDomain, CookieName, DecodedAccessToken, DecodedRefreshToken, GuardConfig, Hostname, MAX_CLOCK_SKEW_SECONDS, PARTY_ID_HEADER, REQUIRE_AUTH_KEY, RefreshCookieOptions, RefreshTokenCookie, Require, SKIP_CSRF_KEY, SessionData, type SessionInfo$1 as SessionInfo, SkipCsrf, Subdomain, TokenExpiry, TokenService, TokenType, UserAgent, UserId, VrittiAuthGuard, hashToken, verifyTokenHash };
package/dist/auth.js CHANGED
@@ -14,7 +14,6 @@ var WORKSPACE_HEADER_ORDER = [
14
14
  var CLIENT_ID_HEADER = "x-vritti-client-id";
15
15
  var PARTY_ID_HEADER = "x-party-id";
16
16
  var MAX_CLOCK_SKEW_SECONDS = 300;
17
- var APP_SESSION_TYPE = "APP";
18
17
 
19
18
  // src/auth/auth.config.ts
20
19
  var AUTH_CONFIG = Symbol("AUTH_CONFIG");
@@ -256,20 +255,25 @@ function getResponseFromContext(host) {
256
255
  }
257
256
  __name(getResponseFromContext, "getResponseFromContext");
258
257
 
259
- // src/auth/decorators/require-app.decorator.ts
258
+ // src/auth/decorators/require.decorator.ts
260
259
  import { SetMetadata } from "@nestjs/common";
261
- var REQUIRE_APP_KEY = "requiredAppTypes";
262
- var RequireApp = /* @__PURE__ */ __name((...types) => SetMetadata(REQUIRE_APP_KEY, types), "RequireApp");
263
-
264
- // src/auth/decorators/require-session.decorator.ts
265
- import { SetMetadata as SetMetadata2 } from "@nestjs/common";
266
- var REQUIRE_SESSION_KEY = "requiredSessionTypes";
267
- var RequireSession = /* @__PURE__ */ __name((...types) => SetMetadata2(REQUIRE_SESSION_KEY, types), "RequireSession");
260
+ var REQUIRE_AUTH_KEY = "requireAuth";
261
+ var AuthType = /* @__PURE__ */ (function(AuthType2) {
262
+ AuthType2["Session"] = "session";
263
+ AuthType2["App"] = "app";
264
+ AuthType2["Cloud"] = "cloud";
265
+ AuthType2["Public"] = "public";
266
+ return AuthType2;
267
+ })({});
268
+ var Require = /* @__PURE__ */ __name((type, ...subtypes) => SetMetadata(REQUIRE_AUTH_KEY, {
269
+ type,
270
+ subtypes
271
+ }), "Require");
268
272
 
269
273
  // src/auth/decorators/skip-csrf.decorator.ts
270
- import { SetMetadata as SetMetadata3 } from "@nestjs/common";
274
+ import { SetMetadata as SetMetadata2 } from "@nestjs/common";
271
275
  var SKIP_CSRF_KEY = "skipCsrf";
272
- var SkipCsrf = /* @__PURE__ */ __name(() => SetMetadata3(SKIP_CSRF_KEY, true), "SkipCsrf");
276
+ var SkipCsrf = /* @__PURE__ */ __name(() => SetMetadata2(SKIP_CSRF_KEY, true), "SkipCsrf");
273
277
 
274
278
  // src/auth/services/token.service.ts
275
279
  import { Inject as Inject2, Injectable as Injectable2, Logger, UnauthorizedException } from "@nestjs/common";
@@ -487,38 +491,64 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
487
491
  context.getHandler(),
488
492
  context.getClass()
489
493
  ]) || csrfExemptTransports.includes(context.getType());
490
- const requiredAppTypes = this.reflector.getAllAndOverride(REQUIRE_APP_KEY, [
491
- context.getHandler(),
492
- context.getClass()
493
- ]);
494
- if (requiredAppTypes) {
495
- return this.handleAppAuth(request, requiredAppTypes, route);
496
- }
497
- const isPublic = this.reflector.getAllAndOverride("isPublic", [
494
+ const requirement = this.reflector.getAllAndOverride(REQUIRE_AUTH_KEY, [
498
495
  context.getHandler(),
499
496
  context.getClass()
500
- ]);
501
- if (isPublic) {
502
- if (!skipCsrf) {
503
- await this.validateCsrf(request, reply);
497
+ ]) ?? {
498
+ type: AuthType.Session,
499
+ subtypes: []
500
+ };
501
+ switch (requirement.type) {
502
+ // Signed server-to-server calls return before CSRF is ever reached. Neither carries a
503
+ // cookie for CSRF to protect, and that holds on REST too — which a transport-level
504
+ // exemption would not cover.
505
+ case AuthType.App:
506
+ return this.handleAppAuth(request, requirement.subtypes, route);
507
+ case AuthType.Cloud:
508
+ return this.handleCloudAuth(request, route);
509
+ case AuthType.Public: {
510
+ if (!skipCsrf) {
511
+ await this.validateCsrf(request, reply);
512
+ }
513
+ this.logger.debug(`${route} \u2014 public endpoint, skipping auth`);
514
+ return true;
515
+ }
516
+ case AuthType.Session: {
517
+ const isSseEndpoint = this.reflector.get(SSE_METADATA, context.getHandler());
518
+ if (isSseEndpoint) {
519
+ this.logger.debug(`${route} \u2014 SSE endpoint, authenticating via refresh cookie`);
520
+ return this.handleSseAuth(request, requirement.subtypes);
521
+ }
522
+ const sessionType = await this.handleHttpAuth(request, requirement.subtypes);
523
+ const csrfExemptSessionTypes = this.config.guard.csrfExemptSessionTypes ?? [];
524
+ if (!skipCsrf && !csrfExemptSessionTypes.includes(sessionType)) {
525
+ await this.validateCsrf(request, reply);
526
+ }
527
+ return true;
504
528
  }
505
- this.logger.debug(`${route} \u2014 public endpoint, skipping auth`);
506
- return true;
507
- }
508
- const requiredSessionTypes = this.reflector.getAllAndOverride(REQUIRE_SESSION_KEY, [
509
- context.getHandler(),
510
- context.getClass()
511
- ]);
512
- const isSseEndpoint = this.reflector.get(SSE_METADATA, context.getHandler());
513
- if (isSseEndpoint) {
514
- this.logger.debug(`${route} \u2014 SSE endpoint, authenticating via refresh cookie`);
515
- return this.handleSseAuth(request, requiredSessionTypes);
516
529
  }
517
- const sessionType = await this.handleHttpAuth(request, requiredSessionTypes);
518
- const csrfExemptSessionTypes = this.config.guard.csrfExemptSessionTypes ?? [];
519
- if (!skipCsrf && !csrfExemptSessionTypes.includes(sessionType)) {
520
- await this.validateCsrf(request, reply);
530
+ }
531
+ /**
532
+ * Authenticates a signed request from the control plane.
533
+ *
534
+ * Same shape as the app branch and for the same reason: verifying the signature needs a key
535
+ * the consuming server holds, so this delegates to `guard.onAuthenticated` and keeps only
536
+ * what this side can do — recognising the decorator and seeding the context.
537
+ *
538
+ * There is no subtype filter. Cloud is one caller, not a family of them.
539
+ */
540
+ async handleCloudAuth(request, route) {
541
+ const onAuthenticated = this.config.guard.onAuthenticated;
542
+ if (!onAuthenticated) {
543
+ this.logger.error(`${route} \u2014 @Require(AuthType.Cloud) requires guard.onAuthenticated to be configured`);
544
+ throw new UnauthorizedException2("Cloud authentication is not configured");
521
545
  }
546
+ const auth = {
547
+ kind: "cloud"
548
+ };
549
+ request.auth = auth;
550
+ await onAuthenticated(this.requestService, auth);
551
+ this.logger.debug(`${route} \u2014 authenticated cloud request`);
522
552
  return true;
523
553
  }
524
554
  /**
@@ -542,16 +572,17 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
542
572
  this.logger.error(`${route} \u2014 @RequireApp() requires guard.onAuthenticated to be configured`);
543
573
  throw new UnauthorizedException2("App authentication is not configured");
544
574
  }
545
- const sessionInfo = {
546
- sessionType: APP_SESSION_TYPE
575
+ const auth = {
576
+ kind: "app"
547
577
  };
548
- request.sessionInfo = sessionInfo;
549
- await onAuthenticated(this.requestService, sessionInfo);
550
- if (requiredAppTypes.length && !requiredAppTypes.includes(sessionInfo.appType ?? "")) {
551
- this.logger.warn(`${route} \u2014 app type ${sessionInfo.appType ?? "unknown"} not in allowed: [${requiredAppTypes.join(", ")}]`);
578
+ request.auth = auth;
579
+ await onAuthenticated(this.requestService, auth);
580
+ const appType = auth.kind === "app" ? auth.appType : void 0;
581
+ if (requiredAppTypes.length && !requiredAppTypes.includes(appType ?? "")) {
582
+ this.logger.warn(`${route} \u2014 app type ${appType ?? "unknown"} not in allowed: [${requiredAppTypes.join(", ")}]`);
552
583
  throw new UnauthorizedException2("This client is not recognised.");
553
584
  }
554
- this.logger.debug(`${route} \u2014 authenticated app (${sessionInfo.appType})`);
585
+ this.logger.debug(`${route} \u2014 authenticated app (${appType})`);
555
586
  return true;
556
587
  }
557
588
  // Authenticates standard HTTP requests using the access token from Authorization header
@@ -575,11 +606,15 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
575
606
  this.logger.warn(`${route} \u2014 session type ${decoded.sessionType} not in allowed: [${requiredSessionTypes.join(", ")}]`);
576
607
  throw new UnauthorizedException2(`${decoded.sessionType} sessions cannot access this endpoint`);
577
608
  }
578
- const { tokenType: _tokenType, refreshTokenHash: _hash, exp: _exp, iat: _iat, ...sessionInfo } = decoded;
579
- request.sessionInfo = sessionInfo;
609
+ const { tokenType: _tokenType, refreshTokenHash: _hash, exp: _exp, iat: _iat, ...claims } = decoded;
610
+ const auth = {
611
+ kind: "session",
612
+ ...claims
613
+ };
614
+ request.auth = auth;
580
615
  const onAuthenticated = this.config.guard.onAuthenticated;
581
616
  if (onAuthenticated) {
582
- await onAuthenticated(this.requestService, request.sessionInfo);
617
+ await onAuthenticated(this.requestService, auth);
583
618
  }
584
619
  this.logger.debug(`${route} \u2014 authenticated user: ${decoded.userId} (${decoded.sessionType})`);
585
620
  return decoded.sessionType;
@@ -596,8 +631,11 @@ var VrittiAuthGuard = class _VrittiAuthGuard {
596
631
  this.logger.warn(`SSE ${request.url} \u2014 session type ${decoded.sessionType} not allowed`);
597
632
  throw new UnauthorizedException2(`${decoded.sessionType} sessions cannot access this endpoint`);
598
633
  }
599
- const { tokenType: _tokenType, exp: _exp, iat: _iat, ...sessionInfo } = decoded;
600
- request.sessionInfo = sessionInfo;
634
+ const { tokenType: _tokenType, exp: _exp, iat: _iat, ...claims } = decoded;
635
+ request.auth = {
636
+ kind: "session",
637
+ ...claims
638
+ };
601
639
  this.logger.debug(`SSE ${request.url} \u2014 authenticated user: ${decoded.userId} (${decoded.sessionType})`);
602
640
  return true;
603
641
  }
@@ -780,10 +818,6 @@ var Hostname = createParamDecorator5((_data, ctx) => {
780
818
  return hostStr.split(":")[0] ?? hostStr;
781
819
  });
782
820
 
783
- // src/auth/decorators/public.decorator.ts
784
- import { SetMetadata as SetMetadata4 } from "@nestjs/common";
785
- var Public = /* @__PURE__ */ __name(() => SetMetadata4("isPublic", true), "Public");
786
-
787
821
  // src/auth/decorators/refresh-cookie-options.decorator.ts
788
822
  import { createParamDecorator as createParamDecorator6 } from "@nestjs/common";
789
823
 
@@ -909,15 +943,14 @@ var RefreshTokenCookie = createParamDecorator7((_data, ctx) => {
909
943
  // src/auth/decorators/session-data.decorator.ts
910
944
  import { createParamDecorator as createParamDecorator8 } from "@nestjs/common";
911
945
  var SessionData = createParamDecorator8((_data, ctx) => {
912
- const request = getRequestFromContext(ctx);
913
- const sessionInfo = request.sessionInfo;
914
- if (!sessionInfo?.sessionId) {
915
- throw new Error("Session info not found on request. Ensure route is protected by auth guard.");
946
+ const auth = getRequestFromContext(ctx).auth;
947
+ if (auth?.kind !== "session") {
948
+ throw new Error(`No session on this request (auth: ${auth?.kind ?? "none"}). @SessionData() is session-only.`);
916
949
  }
917
950
  return {
918
- userId: sessionInfo.userId,
919
- sessionId: sessionInfo.sessionId,
920
- sessionType: sessionInfo.sessionType
951
+ userId: auth.userId,
952
+ sessionId: auth.sessionId,
953
+ sessionType: auth.sessionType
921
954
  };
922
955
  });
923
956
 
@@ -943,19 +976,18 @@ var UserAgent = createParamDecorator10((_data, ctx) => {
943
976
  // src/auth/decorators/user-id.decorator.ts
944
977
  import { createParamDecorator as createParamDecorator11 } from "@nestjs/common";
945
978
  var UserId = createParamDecorator11((_data, ctx) => {
946
- const request = getRequestFromContext(ctx);
947
- const sessionInfo = request.sessionInfo;
948
- if (!sessionInfo?.userId) {
949
- throw new Error("User ID not found on request. Ensure route is protected by auth guard.");
979
+ const auth = getRequestFromContext(ctx).auth;
980
+ if (auth?.kind !== "session") {
981
+ throw new Error(`No user on this request (auth: ${auth?.kind ?? "none"}). @UserId() is session-only.`);
950
982
  }
951
- return sessionInfo.userId;
983
+ return auth.userId;
952
984
  });
953
985
  export {
954
- APP_SESSION_TYPE,
955
986
  AUTH_CONFIG,
956
987
  AUTH_CONFIG_DEFAULTS,
957
988
  AccessToken,
958
989
  AuthConfigModule,
990
+ AuthType,
959
991
  CLIENT_ID_HEADER,
960
992
  ClientIp,
961
993
  CookieDomain,
@@ -963,13 +995,10 @@ export {
963
995
  Hostname,
964
996
  MAX_CLOCK_SKEW_SECONDS,
965
997
  PARTY_ID_HEADER,
966
- Public,
967
- REQUIRE_APP_KEY,
968
- REQUIRE_SESSION_KEY,
998
+ REQUIRE_AUTH_KEY,
969
999
  RefreshCookieOptions,
970
1000
  RefreshTokenCookie,
971
- RequireApp,
972
- RequireSession,
1001
+ Require,
973
1002
  SKIP_CSRF_KEY,
974
1003
  SessionData,
975
1004
  SkipCsrf,