@augmenting-integrations/auth 8.8.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.
- package/dist/server/invitations.d.ts +14 -4
- package/dist/server/invitations.d.ts.map +1 -1
- package/dist/server/profile-settings.d.ts +56 -0
- package/dist/server/profile-settings.d.ts.map +1 -0
- package/dist/server.cjs +125 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +119 -8
- package/dist/server.js.map +1 -1
- package/package.json +8 -6
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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":"AAAA,OAAO,aAAa,CAAC;
|
|
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/
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
|
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 =
|
|
802
|
-
const expiresAt =
|
|
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 =
|
|
815
|
-
const rendered =
|
|
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
|
|
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
|
|
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,
|