@actuate-media/cms-core 0.39.0 → 0.40.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/__tests__/api/users-detail.test.d.ts +2 -0
- package/dist/__tests__/api/users-detail.test.d.ts.map +1 -0
- package/dist/__tests__/api/users-detail.test.js +349 -0
- package/dist/__tests__/api/users-detail.test.js.map +1 -0
- package/dist/__tests__/api/users-invite.test.js +80 -26
- package/dist/__tests__/api/users-invite.test.js.map +1 -1
- package/dist/__tests__/auth/invites.test.d.ts +2 -0
- package/dist/__tests__/auth/invites.test.d.ts.map +1 -0
- package/dist/__tests__/auth/invites.test.js +181 -0
- package/dist/__tests__/auth/invites.test.js.map +1 -0
- package/dist/__tests__/auth/roles.test.d.ts +2 -0
- package/dist/__tests__/auth/roles.test.d.ts.map +1 -0
- package/dist/__tests__/auth/roles.test.js +60 -0
- package/dist/__tests__/auth/roles.test.js.map +1 -0
- package/dist/__tests__/auth/team.test.d.ts +2 -0
- package/dist/__tests__/auth/team.test.d.ts.map +1 -0
- package/dist/__tests__/auth/team.test.js +139 -0
- package/dist/__tests__/auth/team.test.js.map +1 -0
- package/dist/__tests__/auth/user-admin.test.d.ts +2 -0
- package/dist/__tests__/auth/user-admin.test.d.ts.map +1 -0
- package/dist/__tests__/auth/user-admin.test.js +162 -0
- package/dist/__tests__/auth/user-admin.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +837 -16
- package/dist/api/handlers.js.map +1 -1
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/invites.d.ts +71 -0
- package/dist/auth/invites.d.ts.map +1 -0
- package/dist/auth/invites.js +279 -0
- package/dist/auth/invites.js.map +1 -0
- package/dist/auth/reset.d.ts.map +1 -1
- package/dist/auth/reset.js +27 -4
- package/dist/auth/reset.js.map +1 -1
- package/dist/auth/roles.d.ts +51 -0
- package/dist/auth/roles.d.ts.map +1 -0
- package/dist/auth/roles.js +159 -0
- package/dist/auth/roles.js.map +1 -0
- package/dist/auth/team.d.ts +86 -0
- package/dist/auth/team.d.ts.map +1 -0
- package/dist/auth/team.js +270 -0
- package/dist/auth/team.js.map +1 -0
- package/dist/auth/user-admin.d.ts +91 -0
- package/dist/auth/user-admin.d.ts.map +1 -0
- package/dist/auth/user-admin.js +250 -0
- package/dist/auth/user-admin.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
- package/prisma/migrations/0011_user_roles_owner_viewer/migration.sql +6 -0
- package/prisma/migrations/0012_invites_and_user_lifecycle/migration.sql +40 -0
- package/prisma/schema.prisma +43 -0
- package/dist/__tests__/auth/invite.test.d.ts +0 -2
- package/dist/__tests__/auth/invite.test.d.ts.map +0 -1
- package/dist/__tests__/auth/invite.test.js +0 -151
- package/dist/__tests__/auth/invite.test.js.map +0 -1
- package/dist/auth/invite.d.ts +0 -37
- package/dist/auth/invite.d.ts.map +0 -1
- package/dist/auth/invite.js +0 -104
- package/dist/auth/invite.js.map +0 -1
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
-
import { createUserInvite, normalizeInviteRole } from '../../auth/invite.js';
|
|
3
|
-
import { hashToken } from '../../auth/reset.js';
|
|
4
|
-
import { setEmailSender } from '../../email/sender.js';
|
|
5
|
-
function createFakeDb(initial = {}) {
|
|
6
|
-
const users = initial.users ?? [];
|
|
7
|
-
const tokens = [];
|
|
8
|
-
return {
|
|
9
|
-
users,
|
|
10
|
-
tokens,
|
|
11
|
-
user: {
|
|
12
|
-
findFirst: vi.fn(async ({ where }) => {
|
|
13
|
-
const target = where.email.equals.toLowerCase();
|
|
14
|
-
return users.find((u) => u.email.toLowerCase() === target) ?? null;
|
|
15
|
-
}),
|
|
16
|
-
create: vi.fn(async ({ data }) => {
|
|
17
|
-
const u = { id: `u_${users.length + 1}`, ...data };
|
|
18
|
-
users.push(u);
|
|
19
|
-
return u;
|
|
20
|
-
}),
|
|
21
|
-
},
|
|
22
|
-
passwordResetToken: {
|
|
23
|
-
create: vi.fn(async ({ data }) => {
|
|
24
|
-
const t = { id: `rt_${tokens.length + 1}`, ...data };
|
|
25
|
-
tokens.push(t);
|
|
26
|
-
return t;
|
|
27
|
-
}),
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
const BASE_CONFIG = { siteUrl: 'https://example.com', adminPath: '/admin' };
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
setEmailSender(null);
|
|
34
|
-
});
|
|
35
|
-
describe('normalizeInviteRole', () => {
|
|
36
|
-
it('upper-cases and validates known roles', () => {
|
|
37
|
-
expect(normalizeInviteRole('editor')).toBe('EDITOR');
|
|
38
|
-
expect(normalizeInviteRole(' Admin ')).toBe('ADMIN');
|
|
39
|
-
expect(normalizeInviteRole('AUTHOR')).toBe('AUTHOR');
|
|
40
|
-
expect(normalizeInviteRole('client')).toBe('CLIENT');
|
|
41
|
-
});
|
|
42
|
-
it('rejects unknown / empty roles', () => {
|
|
43
|
-
expect(normalizeInviteRole('viewer')).toBeNull();
|
|
44
|
-
expect(normalizeInviteRole('')).toBeNull();
|
|
45
|
-
expect(normalizeInviteRole(undefined)).toBeNull();
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe('createUserInvite', () => {
|
|
49
|
-
it('creates an active user with a valid role and an unusable random password', async () => {
|
|
50
|
-
const db = createFakeDb();
|
|
51
|
-
const result = await createUserInvite(db, { email: 'New@Example.com ', name: ' Jane ', role: 'editor' }, BASE_CONFIG);
|
|
52
|
-
expect(result.success).toBe(true);
|
|
53
|
-
expect(db.user.create).toHaveBeenCalledOnce();
|
|
54
|
-
const created = db.users[0];
|
|
55
|
-
expect(created.email).toBe('new@example.com');
|
|
56
|
-
expect(created.name).toBe('Jane');
|
|
57
|
-
expect(created.role).toBe('EDITOR');
|
|
58
|
-
expect(created.isActive).toBe(true);
|
|
59
|
-
expect(created.isApproved).toBe(true);
|
|
60
|
-
expect(created.emailVerified).toBe(false);
|
|
61
|
-
// Password is a real hash (not blank/plaintext) so the account can't be
|
|
62
|
-
// logged into until the invitee sets their own password.
|
|
63
|
-
expect(created.passwordHash).toMatch(/^pbkdf2:/);
|
|
64
|
-
});
|
|
65
|
-
it('defaults the name to the email local-part when omitted', async () => {
|
|
66
|
-
const db = createFakeDb();
|
|
67
|
-
await createUserInvite(db, { email: 'sam@example.com', role: 'author' }, BASE_CONFIG);
|
|
68
|
-
expect(db.users[0].name).toBe('sam');
|
|
69
|
-
});
|
|
70
|
-
it('creates a single-use reset token and returns a shareable link when no email sender is set', async () => {
|
|
71
|
-
const db = createFakeDb();
|
|
72
|
-
const result = await createUserInvite(db, { email: 'a@example.com', role: 'author' }, BASE_CONFIG);
|
|
73
|
-
expect(result.emailed).toBe(false);
|
|
74
|
-
expect(result.inviteUrl).toContain('https://example.com/admin/reset-password?token=');
|
|
75
|
-
expect(db.passwordResetToken.create).toHaveBeenCalledOnce();
|
|
76
|
-
// The token in the link is the raw token; the DB stores only its hash.
|
|
77
|
-
const tokenInUrl = result.inviteUrl.match(/token=([0-9a-f]{64})/)?.[1];
|
|
78
|
-
expect(tokenInUrl).toBeDefined();
|
|
79
|
-
expect(hashToken(tokenInUrl)).toBe(db.tokens[0].tokenHash);
|
|
80
|
-
// ~7 day expiry
|
|
81
|
-
const ttl = db.tokens[0].expiresAt.getTime() - Date.now();
|
|
82
|
-
expect(ttl).toBeGreaterThan(6.9 * 24 * 60 * 60 * 1000);
|
|
83
|
-
});
|
|
84
|
-
it('emails the invite via the registered sender and hides the link from the result', async () => {
|
|
85
|
-
const send = vi.fn(async (_o) => { });
|
|
86
|
-
setEmailSender({ send });
|
|
87
|
-
const db = createFakeDb();
|
|
88
|
-
const result = await createUserInvite(db, { email: 'a@example.com', name: 'A', role: 'admin' }, { ...BASE_CONFIG, invitedByName: 'Boss', siteName: 'Acme' });
|
|
89
|
-
expect(result.success).toBe(true);
|
|
90
|
-
expect(result.emailed).toBe(true);
|
|
91
|
-
expect(result.inviteUrl).toBeUndefined();
|
|
92
|
-
expect(send).toHaveBeenCalledOnce();
|
|
93
|
-
const sent = send.mock.calls[0][0];
|
|
94
|
-
expect(sent.to).toBe('a@example.com');
|
|
95
|
-
expect(sent.html).toContain('/admin/reset-password?token=');
|
|
96
|
-
});
|
|
97
|
-
it('builds the link from the configured admin path', async () => {
|
|
98
|
-
const db = createFakeDb();
|
|
99
|
-
const result = await createUserInvite(db, { email: 'a@example.com', role: 'author' }, {
|
|
100
|
-
siteUrl: 'https://example.com/',
|
|
101
|
-
adminPath: '/securelogin',
|
|
102
|
-
});
|
|
103
|
-
expect(result.inviteUrl).toContain('https://example.com/securelogin/reset-password?token=');
|
|
104
|
-
expect(result.inviteUrl).not.toContain('//securelogin');
|
|
105
|
-
});
|
|
106
|
-
it('rejects a duplicate email (case-insensitive) without creating a user', async () => {
|
|
107
|
-
const db = createFakeDb({
|
|
108
|
-
users: [
|
|
109
|
-
{
|
|
110
|
-
id: 'u_1',
|
|
111
|
-
email: 'taken@example.com',
|
|
112
|
-
name: 'Taken',
|
|
113
|
-
role: 'EDITOR',
|
|
114
|
-
passwordHash: 'pbkdf2:x',
|
|
115
|
-
isActive: true,
|
|
116
|
-
isApproved: true,
|
|
117
|
-
emailVerified: true,
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
});
|
|
121
|
-
const result = await createUserInvite(db, { email: 'Taken@Example.com', role: 'author' }, BASE_CONFIG);
|
|
122
|
-
expect(result.success).toBe(false);
|
|
123
|
-
expect(result.error).toMatch(/already exists/i);
|
|
124
|
-
expect(db.user.create).not.toHaveBeenCalled();
|
|
125
|
-
});
|
|
126
|
-
it('rejects an invalid email', async () => {
|
|
127
|
-
const db = createFakeDb();
|
|
128
|
-
const result = await createUserInvite(db, { email: 'not-an-email', role: 'author' }, BASE_CONFIG);
|
|
129
|
-
expect(result.success).toBe(false);
|
|
130
|
-
expect(db.user.create).not.toHaveBeenCalled();
|
|
131
|
-
});
|
|
132
|
-
it('rejects an invalid role', async () => {
|
|
133
|
-
const db = createFakeDb();
|
|
134
|
-
const result = await createUserInvite(db, { email: 'a@example.com', role: 'viewer' }, BASE_CONFIG);
|
|
135
|
-
expect(result.success).toBe(false);
|
|
136
|
-
expect(result.error).toMatch(/role/i);
|
|
137
|
-
expect(db.user.create).not.toHaveBeenCalled();
|
|
138
|
-
});
|
|
139
|
-
it('still succeeds when the email send throws (link returned for manual sharing)', async () => {
|
|
140
|
-
const send = vi.fn(async () => {
|
|
141
|
-
throw new Error('smtp down');
|
|
142
|
-
});
|
|
143
|
-
setEmailSender({ send });
|
|
144
|
-
const db = createFakeDb();
|
|
145
|
-
const result = await createUserInvite(db, { email: 'a@example.com', role: 'author' }, BASE_CONFIG);
|
|
146
|
-
expect(result.success).toBe(true);
|
|
147
|
-
expect(result.emailed).toBe(false);
|
|
148
|
-
expect(result.inviteUrl).toContain('/admin/reset-password?token=');
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
//# sourceMappingURL=invite.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invite.test.js","sourceRoot":"","sources":["../../../src/__tests__/auth/invite.test.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/invite.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/** Invitations are valid longer than a password reset — a person has to act. */
|
|
2
|
-
export declare const INVITE_TOKEN_EXPIRY_MS: number;
|
|
3
|
-
export declare const VALID_INVITE_ROLES: readonly ["ADMIN", "EDITOR", "AUTHOR", "CLIENT"];
|
|
4
|
-
export type InviteRole = (typeof VALID_INVITE_ROLES)[number];
|
|
5
|
-
/** Normalise a UI-supplied role to a valid {@link InviteRole}, or null. */
|
|
6
|
-
export declare function normalizeInviteRole(role: string | undefined): InviteRole | null;
|
|
7
|
-
export interface CreateInviteInput {
|
|
8
|
-
email: string;
|
|
9
|
-
name?: string;
|
|
10
|
-
role: string;
|
|
11
|
-
}
|
|
12
|
-
export interface CreateInviteResult {
|
|
13
|
-
success: boolean;
|
|
14
|
-
error?: string;
|
|
15
|
-
/**
|
|
16
|
-
* The accept/set-password URL. Returned only when the invitation email was
|
|
17
|
-
* NOT sent (no sender configured or send failed) so an admin can share it
|
|
18
|
-
* manually. Omitted when the email was delivered.
|
|
19
|
-
*/
|
|
20
|
-
inviteUrl?: string;
|
|
21
|
-
/** Whether the invitation email was actually dispatched. */
|
|
22
|
-
emailed?: boolean;
|
|
23
|
-
user?: {
|
|
24
|
-
id: string;
|
|
25
|
-
email: string;
|
|
26
|
-
name: string;
|
|
27
|
-
role: string;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export declare function createUserInvite(db: any, input: CreateInviteInput, config: {
|
|
31
|
-
siteUrl: string;
|
|
32
|
-
/** Admin mount path; the accept link is `<siteUrl><adminPath>/reset-password`. */
|
|
33
|
-
adminPath?: string;
|
|
34
|
-
invitedByName?: string;
|
|
35
|
-
siteName?: string;
|
|
36
|
-
}): Promise<CreateInviteResult>;
|
|
37
|
-
//# sourceMappingURL=invite.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invite.d.ts","sourceRoot":"","sources":["../../src/auth/invite.ts"],"names":[],"mappings":"AAqBA,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,QAA0B,CAAA;AAE7D,eAAO,MAAM,kBAAkB,kDAAmD,CAAA;AAClF,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAI5D,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,CAI/E;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACjE;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,GAAG,EACP,KAAK,EAAE,iBAAiB,EACxB,MAAM,EAAE;IACN,OAAO,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,GACA,OAAO,CAAC,kBAAkB,CAAC,CA+E7B"}
|
package/dist/auth/invite.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User invitations.
|
|
3
|
-
*
|
|
4
|
-
* Creating an invite provisions the user immediately (so they appear in the
|
|
5
|
-
* users list and access control works) but with an unusable random password.
|
|
6
|
-
* The invitee receives a link to the existing `/reset-password` page, where
|
|
7
|
-
* they set a real password through {@link executePasswordReset}. This reuses
|
|
8
|
-
* the password-reset token (single-use, expiring) and page rather than adding a
|
|
9
|
-
* parallel "accept invite" surface.
|
|
10
|
-
*
|
|
11
|
-
* Email is dispatched through the shared transactional sender registry, so an
|
|
12
|
-
* app that configures email via `emailPlugin()` (the common case) sends invites
|
|
13
|
-
* without any extra wiring. When no sender is configured the invite still
|
|
14
|
-
* succeeds and the caller receives the link to share manually.
|
|
15
|
-
*/
|
|
16
|
-
import { randomBytes } from 'node:crypto';
|
|
17
|
-
import { hashPassword } from './password.js';
|
|
18
|
-
import { generateResetToken } from './reset.js';
|
|
19
|
-
import { userInvitationEmailTemplate } from './invite-email.js';
|
|
20
|
-
import { resolveEmailSender } from '../email/sender.js';
|
|
21
|
-
/** Invitations are valid longer than a password reset — a person has to act. */
|
|
22
|
-
export const INVITE_TOKEN_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
23
|
-
export const VALID_INVITE_ROLES = ['ADMIN', 'EDITOR', 'AUTHOR', 'CLIENT'];
|
|
24
|
-
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
25
|
-
/** Normalise a UI-supplied role to a valid {@link InviteRole}, or null. */
|
|
26
|
-
export function normalizeInviteRole(role) {
|
|
27
|
-
if (!role)
|
|
28
|
-
return null;
|
|
29
|
-
const upper = role.trim().toUpperCase();
|
|
30
|
-
return VALID_INVITE_ROLES.includes(upper) ? upper : null;
|
|
31
|
-
}
|
|
32
|
-
export async function createUserInvite(db, input, config) {
|
|
33
|
-
const email = input.email.toLowerCase().trim();
|
|
34
|
-
if (!EMAIL_RE.test(email)) {
|
|
35
|
-
return { success: false, error: 'Enter a valid email address.' };
|
|
36
|
-
}
|
|
37
|
-
const role = normalizeInviteRole(input.role);
|
|
38
|
-
if (!role) {
|
|
39
|
-
return { success: false, error: 'Select a valid role.' };
|
|
40
|
-
}
|
|
41
|
-
// Case-insensitive existence check so we don't create a duplicate that can
|
|
42
|
-
// never log in (the unique index is on the exact string).
|
|
43
|
-
const existing = await db.user.findFirst({
|
|
44
|
-
where: { email: { equals: email, mode: 'insensitive' } },
|
|
45
|
-
});
|
|
46
|
-
if (existing) {
|
|
47
|
-
return { success: false, error: 'A user with this email already exists.' };
|
|
48
|
-
}
|
|
49
|
-
const name = input.name?.trim() || email.split('@')[0] || email;
|
|
50
|
-
// Unusable random password — the invitee sets a real one via the link. A
|
|
51
|
-
// random hash (rather than null) guarantees the account can never be
|
|
52
|
-
// password-logged-into even if a future code path forgets the null check.
|
|
53
|
-
const passwordHash = await hashPassword(randomBytes(32).toString('hex'));
|
|
54
|
-
const user = await db.user.create({
|
|
55
|
-
data: {
|
|
56
|
-
email,
|
|
57
|
-
name,
|
|
58
|
-
role,
|
|
59
|
-
passwordHash,
|
|
60
|
-
isActive: true,
|
|
61
|
-
isApproved: true,
|
|
62
|
-
emailVerified: false,
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
const { raw, hash } = generateResetToken();
|
|
66
|
-
await db.passwordResetToken.create({
|
|
67
|
-
data: {
|
|
68
|
-
userId: user.id,
|
|
69
|
-
tokenHash: hash,
|
|
70
|
-
expiresAt: new Date(Date.now() + INVITE_TOKEN_EXPIRY_MS),
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
|
-
const base = config.siteUrl.replace(/\/$/, '');
|
|
74
|
-
const adminPath = (config.adminPath ?? '/admin').replace(/\/$/, '');
|
|
75
|
-
const inviteUrl = `${base}${adminPath}/reset-password?token=${raw}`;
|
|
76
|
-
let emailed = false;
|
|
77
|
-
const sender = resolveEmailSender();
|
|
78
|
-
if (sender) {
|
|
79
|
-
const tpl = userInvitationEmailTemplate({
|
|
80
|
-
inviteUrl,
|
|
81
|
-
userName: name,
|
|
82
|
-
role,
|
|
83
|
-
invitedByName: config.invitedByName,
|
|
84
|
-
siteName: config.siteName,
|
|
85
|
-
});
|
|
86
|
-
try {
|
|
87
|
-
await sender.send({ to: email, subject: tpl.subject, html: tpl.html, text: tpl.text });
|
|
88
|
-
emailed = true;
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// Best-effort: the user + token are already persisted, so the admin can
|
|
92
|
-
// still share the returned link. Don't fail the whole invite on a send
|
|
93
|
-
// error.
|
|
94
|
-
emailed = false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
success: true,
|
|
99
|
-
emailed,
|
|
100
|
-
inviteUrl: emailed ? undefined : inviteUrl,
|
|
101
|
-
user: { id: user.id, email: user.email, name: user.name, role: user.role },
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
//# sourceMappingURL=invite.js.map
|
package/dist/auth/invite.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invite.js","sourceRoot":"","sources":["../../src/auth/invite.ts"],"names":[],"mappings":"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"}
|