@augmenting-integrations/auth 8.9.0 → 8.10.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.
@@ -1,5 +1,11 @@
1
1
  import "server-only";
2
2
  import type { Session } from "next-auth";
3
+ export declare const INVITATION_TTL_HOURS = 72;
4
+ /** 32 random bytes -> base64url, no padding. 256 bits of entropy in 43 chars. */
5
+ export declare function defaultGenerateInvitationToken(): string;
6
+ export declare function defaultInvitationExpiresAt(now?: Date): Date;
7
+ export declare function defaultBuildInvitationUrl(token: string, origin: string): string;
8
+ export declare function defaultRenderInvitationEmail(ctx: InvitationEmailContext): RenderedEmail;
3
9
  type AuthFn = () => Promise<Session | null>;
4
10
  export type InvitationSendAppUser = {
5
11
  id: bigint | string | number;
@@ -58,10 +64,14 @@ export type CreateInvitationSendHandlersOptions = {
58
64
  resolveInviteScope: (caller: InvitationSendAppUser, overrideParentId: bigint | null) => bigint | string | number | null;
59
65
  /** Optional allow-list. If provided, body.role must be one of these. */
60
66
  allowedRoles?: string[];
61
- generateInvitationToken: () => string;
62
- invitationExpiresAt: () => Date;
63
- buildInvitationUrl: (token: string, origin: string) => string;
64
- renderInvitationEmail: (ctx: InvitationEmailContext) => RenderedEmail;
67
+ /** Optional. Defaults to {@link defaultGenerateInvitationToken}. */
68
+ generateInvitationToken?: () => string;
69
+ /** Optional. Defaults to {@link defaultInvitationExpiresAt}. */
70
+ invitationExpiresAt?: () => Date;
71
+ /** Optional. Defaults to {@link defaultBuildInvitationUrl}. */
72
+ buildInvitationUrl?: (token: string, origin: string) => string;
73
+ /** Optional. Defaults to {@link defaultRenderInvitationEmail}. */
74
+ renderInvitationEmail?: (ctx: InvitationEmailContext) => RenderedEmail;
65
75
  sendEmail: (args: {
66
76
  to: string;
67
77
  subject: string;
@@ -1 +1 @@
1
- {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/server/invitations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/server/invitations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import "server-only";
2
+ import type { Session } from "next-auth";
3
+ import type { ZodTypeAny, z } from "zod";
4
+ type AuthFn = () => Promise<Session | null>;
5
+ export type ProfileSettingsAppUser = {
6
+ id: bigint | string | number;
7
+ };
8
+ export type ProfileSettingsDb = {
9
+ user: {
10
+ update: (args: {
11
+ where: {
12
+ id: bigint | string | number;
13
+ };
14
+ data: Record<string, unknown>;
15
+ select?: Record<string, true>;
16
+ }) => Promise<Record<string, unknown>>;
17
+ };
18
+ };
19
+ export type ProfileSettingsSection = "profile" | "account";
20
+ export type SectionConfig<TSchema extends ZodTypeAny> = {
21
+ /**
22
+ * Zod schema for the request body. The spoke decides which fields are
23
+ * accepted. Use `.strict()` to reject unknown keys; otherwise Zod will
24
+ * silently strip them and the library cannot enforce that boundary.
25
+ */
26
+ schema: TSchema;
27
+ /**
28
+ * Map the parsed payload to the exact Prisma User patch. The library
29
+ * does NOT infer columns from schema keys; the spoke chooses what gets
30
+ * written. Returning an empty object writes nothing.
31
+ */
32
+ toPatch: (parsed: z.infer<TSchema>) => Record<string, unknown>;
33
+ /** Optional Prisma select shape for the response body. */
34
+ select?: Record<string, true>;
35
+ };
36
+ export type CreateProfileSettingsHandlersOptions<TProfileSchema extends ZodTypeAny = ZodTypeAny, TAccountSchema extends ZodTypeAny = ZodTypeAny> = {
37
+ auth: AuthFn;
38
+ getDb: () => Promise<ProfileSettingsDb>;
39
+ getOrCreateAppUser: (session: Session) => Promise<ProfileSettingsAppUser>;
40
+ sections: {
41
+ profile: SectionConfig<TProfileSchema>;
42
+ account: SectionConfig<TAccountSchema>;
43
+ };
44
+ /** Invoked once per successful update with the section name + applied patch. */
45
+ afterUpdate?: (user: ProfileSettingsAppUser, section: ProfileSettingsSection, patch: Record<string, unknown>) => Promise<void>;
46
+ };
47
+ export declare function createProfileSettingsHandlers<TProfileSchema extends ZodTypeAny, TAccountSchema extends ZodTypeAny>(opts: CreateProfileSettingsHandlersOptions<TProfileSchema, TAccountSchema>): {
48
+ profile: {
49
+ PATCH: (request: Request) => Promise<Response>;
50
+ };
51
+ account: {
52
+ PATCH: (request: Request) => Promise<Response>;
53
+ };
54
+ };
55
+ export {};
56
+ //# sourceMappingURL=profile-settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-settings.d.ts","sourceRoot":"","sources":["../../src/server/profile-settings.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAyBzC,KAAK,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAE5C,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE;QACJ,MAAM,EAAE,CAAC,IAAI,EAAE;YACb,KAAK,EAAE;gBAAE,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;aAAE,CAAC;YACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;SAC/B,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE3D,MAAM,MAAM,aAAa,CAAC,OAAO,SAAS,UAAU,IAAI;IACtD;;;;OAIG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/D,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,oCAAoC,CAC9C,cAAc,SAAS,UAAU,GAAG,UAAU,EAC9C,cAAc,SAAS,UAAU,GAAG,UAAU,IAC5C;IACF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC1E,QAAQ,EAAE;QACR,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;QACvC,OAAO,EAAE,aAAa,CAAC,cAAc,CAAC,CAAC;KACxC,CAAC;IACF,gFAAgF;IAChF,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE,sBAAsB,EAC5B,OAAO,EAAE,sBAAsB,EAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAoDF,wBAAgB,6BAA6B,CAC3C,cAAc,SAAS,UAAU,EACjC,cAAc,SAAS,UAAU,EACjC,IAAI,EAAE,oCAAoC,CAAC,cAAc,EAAE,cAAc,CAAC;;yBArCnD,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;;;yBAA3B,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;;EA0CnD"}
package/dist/server.cjs CHANGED
@@ -33,13 +33,19 @@ __export(server_exports, {
33
33
  AuthError: () => AuthError,
34
34
  IMPERSONATE_COOKIE_NAME: () => IMPERSONATE_COOKIE_NAME,
35
35
  IMPERSONATE_TTL_SECONDS: () => IMPERSONATE_TTL_SECONDS,
36
+ INVITATION_TTL_HOURS: () => INVITATION_TTL_HOURS,
36
37
  createAuth: () => createAuth,
37
38
  createGetOrCreateAppUser: () => createGetOrCreateAppUser,
38
39
  createImpersonateHandlers: () => createImpersonateHandlers,
39
40
  createInvitationHandlers: () => createInvitationHandlers,
40
41
  createInvitationSendHandlers: () => createInvitationSendHandlers,
41
42
  createMeHandler: () => createMeHandler,
43
+ createProfileSettingsHandlers: () => createProfileSettingsHandlers,
42
44
  createSettingsHandlers: () => createSettingsHandlers,
45
+ defaultBuildInvitationUrl: () => defaultBuildInvitationUrl,
46
+ defaultGenerateInvitationToken: () => defaultGenerateInvitationToken,
47
+ defaultInvitationExpiresAt: () => defaultInvitationExpiresAt,
48
+ defaultRenderInvitationEmail: () => defaultRenderInvitationEmail,
43
49
  getUserGroups: () => getUserGroups,
44
50
  hasGroup: () => hasGroup,
45
51
  mintImpersonationToken: () => mintImpersonationToken,
@@ -741,16 +747,121 @@ function createSettingsHandlers(opts) {
741
747
  return { passwordChange, twoFactorSetup, twoFactorVerify, twoFactorDisable };
742
748
  }
743
749
 
744
- // src/server/invitations.ts
750
+ // src/server/profile-settings.ts
745
751
  var import_server_only5 = require("server-only");
746
752
  var import_server4 = require("next/server");
753
+ function unauthorized() {
754
+ return import_server4.NextResponse.json(
755
+ { error: "unauthorized", code: "unauthorized" },
756
+ { status: 401 }
757
+ );
758
+ }
759
+ function jsonValidation2(issues) {
760
+ return import_server4.NextResponse.json({ error: "invalid_body", issues }, { status: 400 });
761
+ }
762
+ function makeHandler(section, config, opts) {
763
+ return async (request) => {
764
+ const session = await opts.auth();
765
+ if (!session?.user) return unauthorized();
766
+ let body;
767
+ try {
768
+ body = await request.json();
769
+ } catch {
770
+ return import_server4.NextResponse.json(
771
+ { error: "invalid_json", code: "validation" },
772
+ { status: 400 }
773
+ );
774
+ }
775
+ const parsed = config.schema.safeParse(body);
776
+ if (!parsed.success) return jsonValidation2(parsed.error.issues);
777
+ const user = await opts.getOrCreateAppUser(session);
778
+ const patch = config.toPatch(parsed.data);
779
+ const db = await opts.getDb();
780
+ const updated = await db.user.update({
781
+ where: { id: user.id },
782
+ data: patch,
783
+ ...config.select ? { select: config.select } : {}
784
+ });
785
+ if (opts.afterUpdate) {
786
+ await opts.afterUpdate(user, section, patch);
787
+ }
788
+ return import_server4.NextResponse.json(updated);
789
+ };
790
+ }
791
+ function createProfileSettingsHandlers(opts) {
792
+ return {
793
+ profile: { PATCH: makeHandler("profile", opts.sections.profile, opts) },
794
+ account: { PATCH: makeHandler("account", opts.sections.account, opts) }
795
+ };
796
+ }
797
+
798
+ // src/server/invitations.ts
799
+ var import_server_only6 = require("server-only");
800
+ var import_node_crypto = require("crypto");
801
+ var import_server5 = require("next/server");
802
+ var INVITATION_TTL_HOURS = 72;
803
+ function defaultGenerateInvitationToken() {
804
+ return (0, import_node_crypto.randomBytes)(32).toString("base64url");
805
+ }
806
+ function defaultInvitationExpiresAt(now = /* @__PURE__ */ new Date()) {
807
+ const expires = new Date(now);
808
+ expires.setHours(expires.getHours() + INVITATION_TTL_HOURS);
809
+ return expires;
810
+ }
811
+ function defaultBuildInvitationUrl(token, origin) {
812
+ const trimmed = origin.replace(/\/+$/u, "");
813
+ return `${trimmed}/invitations/${token}`;
814
+ }
815
+ function defaultRenderInvitationEmail(ctx) {
816
+ const {
817
+ inviterName,
818
+ inviteeEmail,
819
+ intendedRole,
820
+ invitationUrl,
821
+ appDisplayName,
822
+ expiresAt
823
+ } = ctx;
824
+ const subject = `${appDisplayName} | You've been invited`;
825
+ const expiresIso = expiresAt.toISOString();
826
+ const html = `<!doctype html>
827
+ <html>
828
+ <body style="font-family: system-ui, sans-serif; max-width: 560px; margin: 0 auto; padding: 24px;">
829
+ <h1 style="font-size: 20px; margin: 0 0 16px;">You've been invited to ${escapeHtml(appDisplayName)}</h1>
830
+ <p>${escapeHtml(inviterName)} invited <strong>${escapeHtml(inviteeEmail)}</strong> to join their team as <strong>${escapeHtml(intendedRole)}</strong>.</p>
831
+ <p>
832
+ <a href="${escapeAttr(invitationUrl)}" style="display: inline-block; padding: 10px 16px; background: #111; color: #fff; text-decoration: none; border-radius: 6px;">Accept invitation</a>
833
+ </p>
834
+ <p style="color: #555; font-size: 13px;">This invitation expires at ${escapeHtml(expiresIso)}. If you didn't expect this, you can ignore this email.</p>
835
+ </body>
836
+ </html>`;
837
+ const text = [
838
+ `You've been invited to ${appDisplayName}`,
839
+ "",
840
+ `${inviterName} invited ${inviteeEmail} to join their team as ${intendedRole}.`,
841
+ "",
842
+ `Accept the invitation: ${invitationUrl}`,
843
+ "",
844
+ `This invitation expires at ${expiresIso}.`
845
+ ].join("\n");
846
+ return { subject, html, text };
847
+ }
848
+ function escapeHtml(s) {
849
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
850
+ }
851
+ function escapeAttr(s) {
852
+ return escapeHtml(s);
853
+ }
747
854
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
748
855
  var ROLE_RE = /^[a-z_]+$/;
749
856
  var PARENT_RE = /^\d+$/;
750
857
  function jsonError(status, error, code, extra) {
751
- return import_server4.NextResponse.json({ error, code, ...extra ?? {} }, { status });
858
+ return import_server5.NextResponse.json({ error, code, ...extra ?? {} }, { status });
752
859
  }
753
860
  function createInvitationSendHandlers(opts) {
861
+ const generateInvitationToken = opts.generateInvitationToken ?? defaultGenerateInvitationToken;
862
+ const invitationExpiresAt = opts.invitationExpiresAt ?? defaultInvitationExpiresAt;
863
+ const buildInvitationUrl = opts.buildInvitationUrl ?? defaultBuildInvitationUrl;
864
+ const renderInvitationEmail = opts.renderInvitationEmail ?? defaultRenderInvitationEmail;
754
865
  const send = {
755
866
  POST: async (request) => {
756
867
  const session = await opts.auth();
@@ -798,8 +909,8 @@ function createInvitationSendHandlers(opts) {
798
909
  if (existing) {
799
910
  return jsonError(409, "user with that email already exists", "conflict");
800
911
  }
801
- const token = opts.generateInvitationToken();
802
- const expiresAt = opts.invitationExpiresAt();
912
+ const token = generateInvitationToken();
913
+ const expiresAt = invitationExpiresAt();
803
914
  const invitation = await db.invitation.create({
804
915
  data: {
805
916
  token,
@@ -811,8 +922,8 @@ function createInvitationSendHandlers(opts) {
811
922
  }
812
923
  });
813
924
  const origin = opts.appDomainEnv ? `https://${opts.appDomainEnv}` : new URL(request.url).origin;
814
- const invitationUrl = opts.buildInvitationUrl(token, origin);
815
- const rendered = opts.renderInvitationEmail({
925
+ const invitationUrl = buildInvitationUrl(token, origin);
926
+ const rendered = renderInvitationEmail({
816
927
  inviterName: caller.name,
817
928
  inviteeEmail: email,
818
929
  intendedRole: role,
@@ -829,7 +940,7 @@ function createInvitationSendHandlers(opts) {
829
940
  });
830
941
  } catch (err) {
831
942
  const message = err instanceof Error ? err.message : String(err);
832
- return import_server4.NextResponse.json(
943
+ return import_server5.NextResponse.json(
833
944
  {
834
945
  invitationId: invitation.id.toString(),
835
946
  expiresAt: invitation.expires_at.toISOString(),
@@ -838,7 +949,7 @@ function createInvitationSendHandlers(opts) {
838
949
  { status: 202 }
839
950
  );
840
951
  }
841
- return import_server4.NextResponse.json(
952
+ return import_server5.NextResponse.json(
842
953
  {
843
954
  invitationId: invitation.id.toString(),
844
955
  expiresAt: invitation.expires_at.toISOString()
@@ -854,13 +965,19 @@ function createInvitationSendHandlers(opts) {
854
965
  AuthError,
855
966
  IMPERSONATE_COOKIE_NAME,
856
967
  IMPERSONATE_TTL_SECONDS,
968
+ INVITATION_TTL_HOURS,
857
969
  createAuth,
858
970
  createGetOrCreateAppUser,
859
971
  createImpersonateHandlers,
860
972
  createInvitationHandlers,
861
973
  createInvitationSendHandlers,
862
974
  createMeHandler,
975
+ createProfileSettingsHandlers,
863
976
  createSettingsHandlers,
977
+ defaultBuildInvitationUrl,
978
+ defaultGenerateInvitationToken,
979
+ defaultInvitationExpiresAt,
980
+ defaultRenderInvitationEmail,
864
981
  getUserGroups,
865
982
  hasGroup,
866
983
  mintImpersonationToken,