@absolutejs/auth 0.85.0 → 0.86.1
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/CHANGELOG.md +13 -0
- package/changelog.json +35 -0
- package/dist/bun.js +22 -1
- package/dist/bun.js.map +3 -3
- package/dist/cli/migrate.js +6 -6
- package/dist/cli/migrate.js.map +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +73 -30
- package/dist/index.js.map +7 -7
- package/dist/organizations/config.d.ts +2 -0
- package/dist/organizations/operations.d.ts +6 -4
- package/dist/organizations/routes.d.ts +2 -1
- package/dist/organizations/types.d.ts +8 -0
- package/dist/server.js +73 -30
- package/dist/server.js.map +7 -7
- package/package.json +1 -1
|
@@ -15,6 +15,8 @@ export type OrganizationInvitationMessage = {
|
|
|
15
15
|
};
|
|
16
16
|
export type OrganizationsConfig<UserType> = {
|
|
17
17
|
getUserId: (user: UserType) => string;
|
|
18
|
+
/** Return only an independently verified email. Missing evidence denies acceptance. */
|
|
19
|
+
getVerifiedEmail?: (user: UserType) => string | undefined | Promise<string | undefined>;
|
|
18
20
|
organizationStore: OrganizationStore;
|
|
19
21
|
canCreateOrganization?: (user: UserType) => boolean | Promise<boolean>;
|
|
20
22
|
canManageMembers?: (context: {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { OrganizationId } from '../tenancy';
|
|
2
|
-
import type { Organization, OrganizationInvitation,
|
|
3
|
-
export declare const acceptInvitation: ({ organizationStore, token, userId }: {
|
|
2
|
+
import type { Organization, OrganizationInvitation, OrganizationStore } from './types';
|
|
3
|
+
export declare const acceptInvitation: ({ organizationStore, token, userId, verifiedEmail }: {
|
|
4
4
|
organizationStore: OrganizationStore;
|
|
5
5
|
token: string;
|
|
6
6
|
userId: string;
|
|
7
|
-
|
|
7
|
+
/** Provider-verified email, never an unverified profile field or request body. */
|
|
8
|
+
verifiedEmail?: string;
|
|
9
|
+
}) => Promise<import("./types").OrganizationMembership | undefined>;
|
|
8
10
|
export declare const autoAssignOrgsByEmail: ({ email, getOrgsForDomain, organizationStore, roles, userId }: {
|
|
9
11
|
email: string;
|
|
10
12
|
getOrgsForDomain: (domain: string) => Promise<OrganizationId[]> | OrganizationId[];
|
|
@@ -34,6 +36,6 @@ export declare const listUserOrganizations: ({ organizationStore, userId }: {
|
|
|
34
36
|
organizationStore: OrganizationStore;
|
|
35
37
|
userId: string;
|
|
36
38
|
}) => Promise<{
|
|
37
|
-
membership: OrganizationMembership;
|
|
39
|
+
membership: import("./types").OrganizationMembership;
|
|
38
40
|
organization: Organization | undefined;
|
|
39
41
|
}[]>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SessionRecord } from '../types';
|
|
2
2
|
import { type OrganizationsRouteProps } from './config';
|
|
3
|
-
export declare const organizationRoutes: <UserType>({ authSessionStore, canCreateOrganization, canManageMembers, emit, getUserId, invitationDurationMs, onMembershipAdded, onMembershipRemoved, onOrganizationCreated, onSendInvitation, organizationsRoute, organizationStore, ownerRoles }: OrganizationsRouteProps<UserType>) => import("elysia/types").AddRoute<"", "local", {
|
|
3
|
+
export declare const organizationRoutes: <UserType>({ authSessionStore, canCreateOrganization, canManageMembers, emit, getUserId, getVerifiedEmail, invitationDurationMs, onMembershipAdded, onMembershipRemoved, onOrganizationCreated, onSendInvitation, organizationsRoute, organizationStore, ownerRoles }: OrganizationsRouteProps<UserType>) => import("elysia/types").AddRoute<"", "local", {
|
|
4
4
|
decorator: {};
|
|
5
5
|
store: {
|
|
6
6
|
session: SessionRecord<UserType>;
|
|
@@ -100,6 +100,7 @@ export declare const organizationRoutes: <UserType>({ authSessionStore, canCreat
|
|
|
100
100
|
property?: string;
|
|
101
101
|
expected?: string;
|
|
102
102
|
};
|
|
103
|
+
502: "Invitation delivery failed; create a new invitation to retry";
|
|
103
104
|
};
|
|
104
105
|
error: never;
|
|
105
106
|
};
|
|
@@ -29,6 +29,14 @@ export type OrganizationInvitation = {
|
|
|
29
29
|
tokenHash: string;
|
|
30
30
|
};
|
|
31
31
|
export type OrganizationStore = {
|
|
32
|
+
/** Atomically consume a live invitation and add membership. Custom stores must
|
|
33
|
+
* implement this capability; acceptance fails closed without it. */
|
|
34
|
+
acceptInvitation?: (input: {
|
|
35
|
+
tokenHash: string;
|
|
36
|
+
userId: string;
|
|
37
|
+
verifiedEmail: string;
|
|
38
|
+
now: number;
|
|
39
|
+
}) => Promise<OrganizationMembership | undefined>;
|
|
32
40
|
deleteOrganization: (organizationId: OrganizationId) => Promise<void>;
|
|
33
41
|
getInvitation: (invitationId: string) => Promise<OrganizationInvitation | undefined>;
|
|
34
42
|
getInvitationByTokenHash: (tokenHash: string) => Promise<OrganizationInvitation | undefined>;
|
package/dist/server.js
CHANGED
|
@@ -11239,29 +11239,17 @@ init_crypto();
|
|
|
11239
11239
|
var acceptInvitation = async ({
|
|
11240
11240
|
organizationStore,
|
|
11241
11241
|
token,
|
|
11242
|
-
userId
|
|
11242
|
+
userId,
|
|
11243
|
+
verifiedEmail
|
|
11243
11244
|
}) => {
|
|
11244
|
-
|
|
11245
|
-
if (!invitation || invitation.state !== "pending")
|
|
11246
|
-
return;
|
|
11247
|
-
if (invitation.expiresAt < Date.now())
|
|
11245
|
+
if (!verifiedEmail?.trim() || !organizationStore.acceptInvitation)
|
|
11248
11246
|
return;
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11247
|
+
return organizationStore.acceptInvitation({
|
|
11248
|
+
now: Date.now(),
|
|
11249
|
+
tokenHash: await hashToken(token),
|
|
11250
|
+
userId,
|
|
11251
|
+
verifiedEmail: verifiedEmail.trim().toLowerCase()
|
|
11254
11252
|
});
|
|
11255
|
-
const membership = {
|
|
11256
|
-
createdAt: now,
|
|
11257
|
-
organizationId: invitation.organizationId,
|
|
11258
|
-
roles: invitation.roles,
|
|
11259
|
-
status: "active",
|
|
11260
|
-
updatedAt: now,
|
|
11261
|
-
userId
|
|
11262
|
-
};
|
|
11263
|
-
await organizationStore.saveMembership(membership);
|
|
11264
|
-
return membership;
|
|
11265
11253
|
};
|
|
11266
11254
|
var createOrganization = async ({
|
|
11267
11255
|
metadata,
|
|
@@ -11332,6 +11320,7 @@ var organizationRoutes = ({
|
|
|
11332
11320
|
canManageMembers,
|
|
11333
11321
|
emit,
|
|
11334
11322
|
getUserId,
|
|
11323
|
+
getVerifiedEmail,
|
|
11335
11324
|
invitationDurationMs,
|
|
11336
11325
|
onMembershipAdded,
|
|
11337
11326
|
onMembershipRemoved,
|
|
@@ -11438,13 +11427,21 @@ var organizationRoutes = ({
|
|
|
11438
11427
|
organizationStore,
|
|
11439
11428
|
roles: roles ?? []
|
|
11440
11429
|
});
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11430
|
+
try {
|
|
11431
|
+
await onSendInvitation?.({
|
|
11432
|
+
email: invitation.email,
|
|
11433
|
+
expiresAt: invitation.expiresAt,
|
|
11434
|
+
inviterUserId: invitation.inviterUserId,
|
|
11435
|
+
organizationId,
|
|
11436
|
+
token
|
|
11437
|
+
});
|
|
11438
|
+
} catch {
|
|
11439
|
+
await organizationStore.saveInvitation({
|
|
11440
|
+
...invitation,
|
|
11441
|
+
state: "revoked"
|
|
11442
|
+
});
|
|
11443
|
+
return status("Bad Gateway", "Invitation delivery failed; create a new invitation to retry");
|
|
11444
|
+
}
|
|
11448
11445
|
await emit?.({
|
|
11449
11446
|
at: Date.now(),
|
|
11450
11447
|
metadata: { email: invitation.email },
|
|
@@ -11520,7 +11517,8 @@ var organizationRoutes = ({
|
|
|
11520
11517
|
const membership = await acceptInvitation({
|
|
11521
11518
|
organizationStore,
|
|
11522
11519
|
token,
|
|
11523
|
-
userId: getUserId(user)
|
|
11520
|
+
userId: getUserId(user),
|
|
11521
|
+
verifiedEmail: await getVerifiedEmail?.(user)
|
|
11524
11522
|
});
|
|
11525
11523
|
if (!membership) {
|
|
11526
11524
|
return status("Bad Request", "Invalid or expired invitation");
|
|
@@ -11551,7 +11549,7 @@ var organizationRoutes = ({
|
|
|
11551
11549
|
return status("Unauthorized", "Authentication required");
|
|
11552
11550
|
}
|
|
11553
11551
|
const membership = await organizationStore.getMembership(organizationId, getUserId(user));
|
|
11554
|
-
if (membership?.status !== "active") {
|
|
11552
|
+
if (membership?.status !== "active" && !await mayManage(user, organizationId)) {
|
|
11555
11553
|
return status("Forbidden", "Not a member");
|
|
11556
11554
|
}
|
|
11557
11555
|
const members = await organizationStore.listMembershipsByOrganization(organizationId);
|
|
@@ -40417,6 +40415,27 @@ var toInvitation = (row) => ({
|
|
|
40417
40415
|
});
|
|
40418
40416
|
var createNeonOrganizationStore = (databaseUrl) => createPostgresOrganizationStore(createNeonDatabase(databaseUrl));
|
|
40419
40417
|
var createPostgresOrganizationStore = (db) => ({
|
|
40418
|
+
acceptInvitation: async ({ now, tokenHash, userId, verifiedEmail }) => {
|
|
40419
|
+
const claimed = db.$with("claimed").as(db.update(organizationInvitationsTable).set({ accepted_at_ms: now, state: "accepted" }).where(and(eq(organizationInvitationsTable.token_hash, tokenHash), eq(organizationInvitationsTable.state, "pending"), gt(organizationInvitationsTable.expires_at_ms, now), sql`lower(trim(${organizationInvitationsTable.email})) = ${verifiedEmail}`, exists(db.select().from(organizationsTable).where(eq(organizationsTable.organization_id, organizationInvitationsTable.organization_id))), notExists(db.select().from(organizationMembershipsTable).where(and(eq(organizationMembershipsTable.organization_id, organizationInvitationsTable.organization_id), eq(organizationMembershipsTable.user_id, userId), eq(organizationMembershipsTable.status, "suspended")))))).returning({
|
|
40420
|
+
organizationId: organizationInvitationsTable.organization_id,
|
|
40421
|
+
roles: organizationInvitationsTable.roles
|
|
40422
|
+
}));
|
|
40423
|
+
const [row] = await db.with(claimed).insert(organizationMembershipsTable).select(db.select({
|
|
40424
|
+
created_at_ms: sql`${now}`.as("created_at_ms"),
|
|
40425
|
+
organization_id: claimed.organizationId,
|
|
40426
|
+
roles: claimed.roles,
|
|
40427
|
+
status: sql`'active'`.as("status"),
|
|
40428
|
+
updated_at_ms: sql`${now}`.as("updated_at_ms"),
|
|
40429
|
+
user_id: sql`${userId}`.as("user_id")
|
|
40430
|
+
}).from(claimed)).onConflictDoUpdate({
|
|
40431
|
+
set: { user_id: userId },
|
|
40432
|
+
target: [
|
|
40433
|
+
organizationMembershipsTable.organization_id,
|
|
40434
|
+
organizationMembershipsTable.user_id
|
|
40435
|
+
]
|
|
40436
|
+
}).returning();
|
|
40437
|
+
return row && row.status === "active" ? toMembership(row) : undefined;
|
|
40438
|
+
},
|
|
40420
40439
|
deleteOrganization: async (organizationId) => {
|
|
40421
40440
|
await db.delete(organizationsTable).where(eq(organizationsTable.organization_id, organizationId));
|
|
40422
40441
|
},
|
|
@@ -41467,6 +41486,30 @@ var createInMemoryOrganizationStore = () => {
|
|
|
41467
41486
|
const memberships = new Map;
|
|
41468
41487
|
const invitations = new Map;
|
|
41469
41488
|
return {
|
|
41489
|
+
acceptInvitation: async ({ tokenHash, userId, verifiedEmail, now }) => {
|
|
41490
|
+
const invitation = [...invitations.values()].find((value) => value.tokenHash === tokenHash);
|
|
41491
|
+
if (!invitation || invitation.state !== "pending" || invitation.expiresAt <= now || invitation.email.trim().toLowerCase() !== verifiedEmail || !organizations.has(invitation.organizationId))
|
|
41492
|
+
return;
|
|
41493
|
+
const key = membershipKey(invitation.organizationId, userId);
|
|
41494
|
+
const existing = memberships.get(key);
|
|
41495
|
+
if (existing?.status === "suspended")
|
|
41496
|
+
return;
|
|
41497
|
+
const membership = existing ?? {
|
|
41498
|
+
createdAt: now,
|
|
41499
|
+
organizationId: invitation.organizationId,
|
|
41500
|
+
roles: [...invitation.roles],
|
|
41501
|
+
status: "active",
|
|
41502
|
+
updatedAt: now,
|
|
41503
|
+
userId
|
|
41504
|
+
};
|
|
41505
|
+
invitations.set(invitation.invitationId, {
|
|
41506
|
+
...invitation,
|
|
41507
|
+
acceptedAt: now,
|
|
41508
|
+
state: "accepted"
|
|
41509
|
+
});
|
|
41510
|
+
memberships.set(key, membership);
|
|
41511
|
+
return cloneMembership(membership);
|
|
41512
|
+
},
|
|
41470
41513
|
deleteOrganization: async (organizationId) => {
|
|
41471
41514
|
organizations.delete(organizationId);
|
|
41472
41515
|
},
|
|
@@ -42212,5 +42255,5 @@ export {
|
|
|
42212
42255
|
userSessionIdTypebox
|
|
42213
42256
|
};
|
|
42214
42257
|
|
|
42215
|
-
//# debugId=
|
|
42258
|
+
//# debugId=E5C70909EAE2DA0864756E2164756E21
|
|
42216
42259
|
//# sourceMappingURL=server.js.map
|