better-auth 1.7.15 → 1.7.16
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/api/index.d.mts +2 -1
- package/dist/api/index.mjs +3 -2
- package/dist/api/routes/account.mjs +14 -6
- package/dist/api/routes/email-verification.mjs +29 -37
- package/dist/api/routes/sign-in.mjs +2 -2
- package/dist/api/routes/sign-up.mjs +1 -1
- package/dist/api/routes/update-user.mjs +10 -4
- package/dist/api/state/session-source.d.mts +10 -0
- package/dist/api/state/session-source.mjs +28 -0
- package/dist/auth/trusted-origins.mjs +31 -8
- package/dist/client/index.d.mts +2 -1
- package/dist/db/internal-adapter.mjs +24 -6
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/oauth2/link-account.d.mts +3 -5
- package/dist/oauth2/link-account.mjs +33 -3
- package/dist/oauth2/state.d.mts +6 -5
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/access/statement.d.mts +10 -10
- package/dist/plugins/admin/access/statement.mjs +2 -0
- package/dist/plugins/admin/admin.d.mts +4 -3
- package/dist/plugins/admin/client.d.mts +5 -4
- package/dist/plugins/admin/error-codes.d.mts +1 -0
- package/dist/plugins/admin/error-codes.mjs +1 -0
- package/dist/plugins/admin/routes.mjs +8 -0
- package/dist/plugins/anonymous/index.mjs +2 -1
- package/dist/plugins/device-authorization/routes.mjs +3 -3
- package/dist/plugins/email-otp/routes.mjs +33 -50
- package/dist/plugins/generic-oauth/index.mjs +54 -30
- package/dist/plugins/index.d.mts +2 -1
- package/dist/plugins/index.mjs +2 -1
- package/dist/plugins/oauth-proxy/index.mjs +1 -1
- package/dist/plugins/one-tap/index.mjs +1 -1
- package/dist/plugins/organization/adapter.mjs +1 -0
- package/dist/plugins/organization/client.d.mts +1 -0
- package/dist/plugins/organization/error-codes.d.mts +1 -0
- package/dist/plugins/organization/error-codes.mjs +1 -0
- package/dist/plugins/organization/has-permission.d.mts +2 -3
- package/dist/plugins/organization/has-permission.mjs +62 -23
- package/dist/plugins/organization/index.d.mts +2 -1
- package/dist/plugins/organization/index.mjs +2 -1
- package/dist/plugins/organization/organization.d.mts +2 -2
- package/dist/plugins/organization/organization.mjs +1 -3
- package/dist/plugins/organization/permission.mjs +3 -3
- package/dist/plugins/organization/roles.d.mts +8 -0
- package/dist/plugins/organization/roles.mjs +26 -0
- package/dist/plugins/organization/routes/crud-access-control.mjs +2 -1
- package/dist/plugins/organization/routes/crud-invites.mjs +22 -11
- package/dist/plugins/organization/routes/crud-members.mjs +10 -14
- package/dist/plugins/organization/routes/crud-org.mjs +12 -3
- package/dist/plugins/phone-number/client.d.mts +2 -0
- package/dist/plugins/phone-number/error-codes.d.mts +2 -0
- package/dist/plugins/phone-number/error-codes.mjs +2 -0
- package/dist/plugins/phone-number/index.d.mts +25 -1
- package/dist/plugins/phone-number/index.mjs +22 -6
- package/dist/plugins/phone-number/routes.mjs +34 -35
- package/dist/plugins/two-factor/index.d.mts +9 -2
- package/dist/plugins/two-factor/index.mjs +73 -26
- package/dist/plugins/two-factor/verify-two-factor.mjs +2 -2
- package/dist/plugins/username/index.mjs +1 -1
- package/dist/state.d.mts +6 -0
- package/dist/state.mjs +5 -2
- package/dist/utils/index.d.mts +2 -2
- package/package.json +9 -9
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toBoolean } from "../../utils/boolean.mjs";
|
|
1
2
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
2
3
|
import { GENERIC_OAUTH_ERROR_CODES } from "./error-codes.mjs";
|
|
3
4
|
import { auth0 } from "./providers/auth0.mjs";
|
|
@@ -31,6 +32,17 @@ const DISCOVERY_RETRY_TIMEOUT_MS = 1e4;
|
|
|
31
32
|
* plugin may replace `socialProviders`.
|
|
32
33
|
*/
|
|
33
34
|
const getPendingDiscovery = (ctx) => ctx.genericOAuthPendingDiscovery;
|
|
35
|
+
/**
|
|
36
|
+
* Reads ID token claims, or null for a token that is not a JWS, so a
|
|
37
|
+
* malformed token fails the sign-in instead of throwing.
|
|
38
|
+
*/
|
|
39
|
+
const readIdTokenClaims = (idToken) => {
|
|
40
|
+
try {
|
|
41
|
+
return decodeJwt(idToken);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
34
46
|
async function fetchDiscovery(url, headers, timeout) {
|
|
35
47
|
const result = await betterFetch(url, {
|
|
36
48
|
method: "GET",
|
|
@@ -45,17 +57,17 @@ async function fetchDiscovery(url, headers, timeout) {
|
|
|
45
57
|
}
|
|
46
58
|
return result.data;
|
|
47
59
|
}
|
|
48
|
-
async function fetchUserInfo(tokens, userInfoUrl) {
|
|
49
|
-
if (tokens.idToken)
|
|
50
|
-
const decoded =
|
|
60
|
+
async function fetchUserInfo(tokens, userInfoUrl, idTokenClaims) {
|
|
61
|
+
if (tokens.idToken) {
|
|
62
|
+
const decoded = idTokenClaims ?? readIdTokenClaims(tokens.idToken);
|
|
51
63
|
if (decoded?.sub && decoded?.email) return {
|
|
52
64
|
id: decoded.sub,
|
|
53
|
-
emailVerified: decoded.email_verified,
|
|
65
|
+
emailVerified: toBoolean(decoded.email_verified),
|
|
54
66
|
image: decoded.picture,
|
|
55
67
|
...decoded
|
|
56
68
|
};
|
|
57
|
-
}
|
|
58
|
-
if (!userInfoUrl) return null;
|
|
69
|
+
}
|
|
70
|
+
if (!userInfoUrl || !tokens.accessToken) return null;
|
|
59
71
|
const userInfo = await betterFetch(userInfoUrl, {
|
|
60
72
|
method: "GET",
|
|
61
73
|
headers: { Authorization: `Bearer ${tokens.accessToken}` }
|
|
@@ -64,7 +76,7 @@ async function fetchUserInfo(tokens, userInfoUrl) {
|
|
|
64
76
|
return {
|
|
65
77
|
...userInfo.data,
|
|
66
78
|
email: userInfo.data.email,
|
|
67
|
-
emailVerified: userInfo.data.email_verified
|
|
79
|
+
emailVerified: toBoolean(userInfo.data.email_verified),
|
|
68
80
|
image: userInfo.data.picture,
|
|
69
81
|
name: userInfo.data.name
|
|
70
82
|
};
|
|
@@ -140,6 +152,27 @@ const genericOAuth = (options) => {
|
|
|
140
152
|
if (c.clientSecret && isSecretlessTokenEndpointAuth(tokenEndpointAuth)) throw new Error(`Provider "${c.providerId}": tokenEndpointAuth.method "${tokenEndpointAuth?.method}" cannot be combined with clientSecret`);
|
|
141
153
|
if (!c.clientSecret && isClientSecretTokenEndpointAuth(tokenEndpointAuth)) throw new Error(`Provider "${c.providerId}": tokenEndpointAuth.method "${tokenEndpointAuth?.method}" requires clientSecret`);
|
|
142
154
|
if (!c.clientSecret && !tokenEndpointAuth && c.authentication === "basic") throw new Error(`Provider "${c.providerId}": authentication "basic" requires clientSecret`);
|
|
155
|
+
const resolveUserInfo = async (tokens, idTokenClaims) => {
|
|
156
|
+
const raw = c.getUserInfo ? await c.getUserInfo(tokens) : await fetchUserInfo(tokens, userInfoUrl, idTokenClaims);
|
|
157
|
+
if (!raw) return null;
|
|
158
|
+
const requiresSubjectMatch = !c.getUserInfo || raw.sub != null;
|
|
159
|
+
if (idTokenClaims?.sub !== void 0 && requiresSubjectMatch && String(raw.sub) !== idTokenClaims.sub) return null;
|
|
160
|
+
const mapped = c.mapProfileToUser ? await c.mapProfileToUser(raw) : {};
|
|
161
|
+
const user = {
|
|
162
|
+
email: raw.email,
|
|
163
|
+
emailVerified: toBoolean(raw.emailVerified),
|
|
164
|
+
image: raw.image,
|
|
165
|
+
name: raw.name,
|
|
166
|
+
...mapped
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
user: {
|
|
170
|
+
...user,
|
|
171
|
+
image: user.image ?? void 0
|
|
172
|
+
},
|
|
173
|
+
data: raw
|
|
174
|
+
};
|
|
175
|
+
};
|
|
143
176
|
const accountSubject = c.accountSubject;
|
|
144
177
|
const provider = {
|
|
145
178
|
id: c.providerId,
|
|
@@ -151,7 +184,9 @@ const genericOAuth = (options) => {
|
|
|
151
184
|
tokens,
|
|
152
185
|
profile: genericProfile
|
|
153
186
|
});
|
|
154
|
-
|
|
187
|
+
if (!isOidc) return genericProfile.id ?? "";
|
|
188
|
+
if (idTokenConfig && tokens.idToken) return readIdTokenClaims(tokens.idToken)?.sub ?? "";
|
|
189
|
+
return genericProfile.sub ?? "";
|
|
155
190
|
},
|
|
156
191
|
idToken: idTokenConfig,
|
|
157
192
|
requiresIdTokenNonce: idTokenConfig !== void 0 && c.disableIdTokenNonceBinding !== true,
|
|
@@ -225,31 +260,20 @@ const genericOAuth = (options) => {
|
|
|
225
260
|
});
|
|
226
261
|
return applyDefaultAccessTokenExpiry(tokens, c.accessTokenExpiresIn);
|
|
227
262
|
},
|
|
263
|
+
async getUserInfoFromIdToken(idToken) {
|
|
264
|
+
const claims = provider.idToken ? readIdTokenClaims(idToken) : void 0;
|
|
265
|
+
if (claims === null) return null;
|
|
266
|
+
return resolveUserInfo({ idToken }, claims);
|
|
267
|
+
},
|
|
228
268
|
async getUserInfo(tokens) {
|
|
229
269
|
const { expectedIdTokenNonce, ...oauthTokens } = tokens;
|
|
230
|
-
if (oauthTokens.idToken
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
270
|
+
if (!oauthTokens.idToken || !provider.idToken) return resolveUserInfo(oauthTokens);
|
|
271
|
+
const claims = await verifyProviderIdToken(provider, oauthTokens.idToken, expectedIdTokenNonce) ? readIdTokenClaims(oauthTokens.idToken) : null;
|
|
272
|
+
if (!claims) {
|
|
273
|
+
ctx.logger.error(`Provider "${c.providerId}": id_token failed verification against the discovery JWKS or expected nonce`);
|
|
274
|
+
return null;
|
|
235
275
|
}
|
|
236
|
-
|
|
237
|
-
if (!raw) return null;
|
|
238
|
-
const mapped = c.mapProfileToUser ? await c.mapProfileToUser(raw) : {};
|
|
239
|
-
const user = {
|
|
240
|
-
email: raw.email,
|
|
241
|
-
emailVerified: raw.emailVerified,
|
|
242
|
-
image: raw.image,
|
|
243
|
-
name: raw.name,
|
|
244
|
-
...mapped
|
|
245
|
-
};
|
|
246
|
-
return {
|
|
247
|
-
user: {
|
|
248
|
-
...user,
|
|
249
|
-
image: user.image ?? void 0
|
|
250
|
-
},
|
|
251
|
-
data: raw
|
|
252
|
-
};
|
|
276
|
+
return resolveUserInfo(oauthTokens, claims);
|
|
253
277
|
},
|
|
254
278
|
async refreshAccessToken(refreshToken, refreshCtx) {
|
|
255
279
|
if (!tokenUrl) throw APIError.from("BAD_REQUEST", GENERIC_OAUTH_ERROR_CODES.TOKEN_URL_NOT_FOUND);
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -67,6 +67,7 @@ import { twoFactor } from "./two-factor/index.mjs";
|
|
|
67
67
|
import { USERNAME_ERROR_CODES } from "./username/error-codes.mjs";
|
|
68
68
|
import { UsernameOptions, UsernamePlugin, UsernamePluginWithoutDisplayUsername, username } from "./username/index.mjs";
|
|
69
69
|
import { hasPermission } from "./organization/has-permission.mjs";
|
|
70
|
-
import {
|
|
70
|
+
import { parseRoles } from "./organization/roles.mjs";
|
|
71
|
+
import { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, OrganizationCreator, OrganizationEndpoints, OrganizationPlugin, TeamEndpoints, organization } from "./organization/organization.mjs";
|
|
71
72
|
import "./organization/index.mjs";
|
|
72
73
|
export { AccessControl, type AdminOptions, type AnonymousOptions, type AnonymousSession, ArrayElement, type Auth0Options, AuthorizeResponse, type BackupCodeOptions, type BaseCaptchaOptions, BaseOAuthProviderOptions, BearerOptions, type BotIdVerification, type CaptchaFoxOptions, type CaptchaOptions, type CloudflareTurnstileOptions, CustomSessionPluginOptions, DefaultOrganizationPlugin, DeviceAuthorizationGrant, DeviceAuthorizationGrantAuthorization, DeviceAuthorizationOptions, DeviceAuthorizationPluginOptions, DeviceAuthorizationRequest, type DeviceCode, type DeviceCodeRedemptionAuthorization, type DeviceCodeRedemptionResult, DynamicAccessControlEndpoints, MULTI_SESSION_ERROR_CODES as ERROR_CODES, type EmailOTPOptions, ExactRoleStatements, type FieldSchema, type GenericOAuthConfig, type GenericOAuthOptions, type GenericOAuthUserInfo, type GoogleRecaptchaOptions, type GumroadOptions, type HCaptchaOptions, HIDE_METADATA, HaveIBeenPwnedOptions, type HubSpotOptions, type InferAdminRolesFromOption, type InferInvitation, type InferMember, InferOptionSchema, type InferOrganization, type InferOrganizationRolesFromOption, type InferOrganizationZodRolesFromOption, InferPluginContext, InferPluginErrorCodes, InferPluginIDs, type InferTeam, type Invitation, type InvitationInput, type InvitationStatus, type JWKOptions, type JWSAlgorithms, type Jwk, type JwtOptions, type KeycloakOptions, LastLoginMethodOptions, type LineOptions, type LoginResult, MagicLinkOptions, type Member, type MemberInput, type MicrosoftEntraIdOptions, MultiSessionConfig, OAUTH_POPUP_COMPLETE_SCRIPT, OAUTH_POPUP_DATA_ELEMENT_ID, OAUTH_POPUP_ERROR_CODES, OAUTH_POPUP_MESSAGE_TYPE, OAUTH_POPUP_SCRIPT_CSP_HASH, type OAuthPopupData, type OAuthPopupMessage, OAuthProxyOptions, type OTPOptions, type OktaOptions, OneTapOptions, OneTimeTokenOptions, type OpenAPIModelSchema, OpenAPIOptions, type OpenAPIParameter, type OpenAPISchema, type Organization, OrganizationCreator, OrganizationEndpoints, type OrganizationInput, type OrganizationOptions, OrganizationPlugin, type OrganizationRole, type OrganizationSchema, POPUP_MARKER_COOKIE, type Path, type PatreonOptions, type PhoneNumberOptions, type Provider, type ResolvedSigningKey, Role, RoleAuthorizeRequest, RoleInput, RoleStatements, SIWEPluginOptions, type SessionWithImpersonatedBy, type SlackOptions, Statements, SubArray, Subset, type TOTPOptions, TWO_FACTOR_ERROR_CODES, type Team, TeamEndpoints, type TeamInput, type TeamMember, type TeamMemberInput, type TestAuthOptions, type TestCookie, type TestHelpers, type TestUtilsOptions, type TimeString, TwoFactorOptions, TwoFactorProvider, TwoFactorTable, USERNAME_ERROR_CODES, type UserWithAnonymous, type UserWithPhoneNumber, type UserWithRole, UserWithTwoFactor, UsernameOptions, UsernamePlugin, UsernamePluginWithoutDisplayUsername, type ValidateRequestContext, type VercelBotIdOptions, type YandexOptions, admin, anonymous, auth0, type backupCode2fa, bearer, captcha, createAccessControl, createJwk, customSession, type defaultRolesSchema, deviceAuthorization, deviceAuthorizationOptionsSchema, emailOTP, type encodeBackupCodes, type generateBackupCodes, generateExportedKeyPair, type generator, genericOAuth, type getBackupCodes, getJwtToken, getOrgAdapter, gumroad, hasPermission, haveIBeenPwned, hubspot, type invitationSchema, type invitationStatus, isPasswordCompromised, jwt, keycloak, lastLoginMethod, line, magicLink, type memberSchema, microsoftEntraId, type ms, multiSession, oAuthProxy, oauthPopup, okta, oneTap, oneTimeToken, openAPI, organization, type organizationRoleSchema, type organizationSchema, type otp2fa, parseRoles, patreon, phoneNumber, redeemDeviceCode, resolveSigningKey, role, type roleSchema, type sec, signJWT, siwe, slack, type teamMemberSchema, type teamSchema, testUtils, toExpJWT, type totp2fa, twoFactor, twoFactorClient, username, type verifyBackupCode, verifyJWT, yandex };
|
package/dist/plugins/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { createAccessControl, role } from "./access/access.mjs";
|
|
|
3
3
|
import { MULTI_SESSION_ERROR_CODES } from "./multi-session/error-codes.mjs";
|
|
4
4
|
import { OAUTH_POPUP_DATA_ELEMENT_ID, OAUTH_POPUP_MESSAGE_TYPE, POPUP_MARKER_COOKIE } from "./oauth-popup/constants.mjs";
|
|
5
5
|
import { OAUTH_POPUP_ERROR_CODES } from "./oauth-popup/error-codes.mjs";
|
|
6
|
+
import { parseRoles } from "./organization/roles.mjs";
|
|
6
7
|
import { TWO_FACTOR_ERROR_CODES } from "./two-factor/error-code.mjs";
|
|
7
8
|
import { twoFactorClient } from "./two-factor/client.mjs";
|
|
8
9
|
import { USERNAME_ERROR_CODES } from "./username/error-codes.mjs";
|
|
@@ -40,7 +41,7 @@ import { oneTimeToken } from "./one-time-token/index.mjs";
|
|
|
40
41
|
import { openAPI } from "./open-api/index.mjs";
|
|
41
42
|
import { getOrgAdapter } from "./organization/adapter.mjs";
|
|
42
43
|
import { hasPermission } from "./organization/has-permission.mjs";
|
|
43
|
-
import { organization
|
|
44
|
+
import { organization } from "./organization/organization.mjs";
|
|
44
45
|
import { phoneNumber } from "./phone-number/index.mjs";
|
|
45
46
|
import { siwe } from "./siwe/index.mjs";
|
|
46
47
|
import { testUtils } from "./test-utils/index.mjs";
|
|
@@ -4,10 +4,10 @@ import { originCheck } from "../../api/middlewares/origin-check.mjs";
|
|
|
4
4
|
import { parseSetCookieHeader } from "../../cookies/cookie-utils.mjs";
|
|
5
5
|
import { symmetricDecrypt, symmetricEncrypt } from "../../crypto/index.mjs";
|
|
6
6
|
import { setSessionCookie } from "../../cookies/index.mjs";
|
|
7
|
+
import { derivePurposeKey } from "../../crypto/purpose.mjs";
|
|
7
8
|
import { redirectOnError } from "../../oauth2/errors.mjs";
|
|
8
9
|
import { handleOAuthUserInfo, linkOAuthAccount } from "../../oauth2/link-account.mjs";
|
|
9
10
|
import { setOAuthState } from "../../api/state/oauth.mjs";
|
|
10
|
-
import { derivePurposeKey } from "../../crypto/purpose.mjs";
|
|
11
11
|
import { getAuthStateVerificationIdentifier, parseGenericState } from "../../state.mjs";
|
|
12
12
|
import { exchangeOAuthCallback } from "../../oauth2/callback.mjs";
|
|
13
13
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
@@ -3,10 +3,10 @@ import { setSessionCookie } from "../../cookies/index.mjs";
|
|
|
3
3
|
import { getAwaitableValue } from "../../context/helpers.mjs";
|
|
4
4
|
import { toOAuthProfileRecord } from "../../oauth2/account-key.mjs";
|
|
5
5
|
import { OAUTH_CALLBACK_ERROR_CODES } from "../../oauth2/errors.mjs";
|
|
6
|
+
import { toBoolean } from "../../utils/boolean.mjs";
|
|
6
7
|
import { handleOAuthUserInfo } from "../../oauth2/link-account.mjs";
|
|
7
8
|
import { APIError as APIError$1 } from "../../api/index.mjs";
|
|
8
9
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
9
|
-
import { toBoolean } from "../../utils/boolean.mjs";
|
|
10
10
|
import { BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
11
11
|
import { isGoogleHostedDomainAllowed, verifyGoogleIdToken } from "@better-auth/core/social-providers";
|
|
12
12
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
@@ -735,6 +735,7 @@ const getOrgAdapter = (context, options) => {
|
|
|
735
735
|
return await adapter.create({
|
|
736
736
|
model: "invitation",
|
|
737
737
|
data: {
|
|
738
|
+
...context.options.advanced?.database?.generateId === void 0 && { id: context.generateId({ model: "invitation" }) },
|
|
738
739
|
status: "pending",
|
|
739
740
|
expiresAt,
|
|
740
741
|
createdAt: /* @__PURE__ */ new Date(),
|
|
@@ -241,6 +241,7 @@ export declare const organizationClient: <CO extends OrganizationClientOptions>(
|
|
|
241
241
|
EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION">;
|
|
242
242
|
EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION">;
|
|
243
243
|
YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION">;
|
|
244
|
+
YOU_ARE_NOT_ALLOWED_TO_LIST_INVITATIONS: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_LIST_INVITATIONS">;
|
|
244
245
|
INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION: import("@better-auth/core/utils/error-codes").RawError<"INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION">;
|
|
245
246
|
YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE">;
|
|
246
247
|
FAILED_TO_RETRIEVE_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"FAILED_TO_RETRIEVE_INVITATION">;
|
|
@@ -25,6 +25,7 @@ export declare const ORGANIZATION_ERROR_CODES: {
|
|
|
25
25
|
EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION">;
|
|
26
26
|
EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION">;
|
|
27
27
|
YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION">;
|
|
28
|
+
YOU_ARE_NOT_ALLOWED_TO_LIST_INVITATIONS: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_LIST_INVITATIONS">;
|
|
28
29
|
INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION: import("@better-auth/core/utils/error-codes").RawError<"INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION">;
|
|
29
30
|
YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE">;
|
|
30
31
|
FAILED_TO_RETRIEVE_INVITATION: import("@better-auth/core/utils/error-codes").RawError<"FAILED_TO_RETRIEVE_INVITATION">;
|
|
@@ -26,6 +26,7 @@ const ORGANIZATION_ERROR_CODES = defineErrorCodes({
|
|
|
26
26
|
EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION: "Email verification required before accepting or rejecting invitation",
|
|
27
27
|
EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION: "Email verification required to view or list invitations for the session email",
|
|
28
28
|
YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION: "You are not allowed to cancel this invitation",
|
|
29
|
+
YOU_ARE_NOT_ALLOWED_TO_LIST_INVITATIONS: "You are not allowed to list invitations for this organization",
|
|
29
30
|
INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION: "Inviter is no longer a member of the organization",
|
|
30
31
|
YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE: "You are not allowed to invite a user with this role",
|
|
31
32
|
FAILED_TO_RETRIEVE_INVITATION: "Failed to retrieve invitation",
|
|
@@ -4,9 +4,8 @@ import { GenericEndpointContext } from "@better-auth/core";
|
|
|
4
4
|
export declare const hasPermission: (input: {
|
|
5
5
|
organizationId: string;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* hasPermission calls in a row.
|
|
7
|
+
* Reuse roles already loaded for this organization in the current request.
|
|
8
|
+
* The cache ends with the request.
|
|
10
9
|
*
|
|
11
10
|
* @default false
|
|
12
11
|
*/
|
|
@@ -1,34 +1,73 @@
|
|
|
1
1
|
import { APIError } from "../../api/index.mjs";
|
|
2
2
|
import { defaultRoles } from "./access/statement.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { parseRoleList } from "./roles.mjs";
|
|
4
|
+
import { hasPermissionFn } from "./permission.mjs";
|
|
5
|
+
import { defineRequestState, hasRequestState } from "@better-auth/core/context";
|
|
4
6
|
import * as z from "zod";
|
|
5
7
|
//#region src/plugins/organization/has-permission.ts
|
|
6
8
|
const rolePermissionsSchema = z.record(z.string(), z.array(z.string()));
|
|
9
|
+
/**
|
|
10
|
+
* Request-scoped role cache, isolated by adapter and organization options before
|
|
11
|
+
* organization id. Nested calls to another auth instance can share request state,
|
|
12
|
+
* but must never replace the caller's permission definitions.
|
|
13
|
+
*/
|
|
14
|
+
const resolvedOrganizationRoles = defineRequestState(() => /* @__PURE__ */ new WeakMap());
|
|
15
|
+
async function getRequestScopedRolesCache(ctx, options) {
|
|
16
|
+
if (!await hasRequestState()) return;
|
|
17
|
+
const adapters = await resolvedOrganizationRoles.get();
|
|
18
|
+
const configurations = adapters.get(ctx.context.adapter) ?? /* @__PURE__ */ new WeakMap();
|
|
19
|
+
adapters.set(ctx.context.adapter, configurations);
|
|
20
|
+
const roles = configurations.get(options) ?? /* @__PURE__ */ new Map();
|
|
21
|
+
configurations.set(options, roles);
|
|
22
|
+
return roles;
|
|
23
|
+
}
|
|
7
24
|
const hasPermission = async (input, ctx) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const acRoles = { ...input.options.roles || defaultRoles };
|
|
26
|
+
const ac = input.options.ac;
|
|
27
|
+
if (!ctx || !input.organizationId || !input.options.dynamicAccessControl?.enabled || !ac) return hasPermissionFn(input, acRoles);
|
|
28
|
+
const cache = await getRequestScopedRolesCache(ctx, input.options);
|
|
29
|
+
const cached = input.useMemoryCache ? cache?.get(input.organizationId) : void 0;
|
|
30
|
+
if (cached) return hasPermissionFn(input, cached);
|
|
31
|
+
const roles = await ctx.context.adapter.findMany({
|
|
32
|
+
model: "organizationRole",
|
|
33
|
+
where: [{
|
|
34
|
+
field: "organizationId",
|
|
35
|
+
value: input.organizationId
|
|
36
|
+
}]
|
|
37
|
+
});
|
|
38
|
+
for (const { role, permission: permissionsString } of roles) {
|
|
39
|
+
const permissions = JSON.parse(permissionsString);
|
|
40
|
+
const result = rolePermissionsSchema.safeParse(permissions);
|
|
41
|
+
if (!result.success) {
|
|
42
|
+
ctx.context.logger.error("[hasPermission] Invalid permissions for role " + role, { permissions });
|
|
43
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: "Invalid permissions for role " + role });
|
|
27
44
|
}
|
|
45
|
+
const merged = { ...acRoles[role]?.statements };
|
|
46
|
+
for (const [key, actions] of Object.entries(result.data)) merged[key] = [.../* @__PURE__ */ new Set([...merged[key] ?? [], ...actions])];
|
|
47
|
+
acRoles[role] = ac.newRole(merged);
|
|
28
48
|
}
|
|
29
|
-
|
|
30
|
-
cacheAllRoles.set(input.organizationId, acRoles);
|
|
49
|
+
cache?.set(input.organizationId, acRoles);
|
|
31
50
|
return hasPermissionFn(input, acRoles);
|
|
32
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Whether a member may see the organization's pending invitations.
|
|
54
|
+
*
|
|
55
|
+
* Invitation ids act as invitation links. Members who can create invitations
|
|
56
|
+
* can mint such links anyway; any other role, including one that may only
|
|
57
|
+
* cancel, must not see them.
|
|
58
|
+
*/
|
|
59
|
+
const canViewInvitations = (input, ctx) => hasPermission({
|
|
60
|
+
...input,
|
|
61
|
+
permissions: { invitation: ["create"] }
|
|
62
|
+
}, ctx);
|
|
63
|
+
/**
|
|
64
|
+
* Drops invitations that grant the creator role unless the member holds it,
|
|
65
|
+
* matching the rule that only the creator role can hand it out.
|
|
66
|
+
*/
|
|
67
|
+
const withoutCreatorRoleInvitations = (invitations, input) => {
|
|
68
|
+
const creatorRole = input.options.creatorRole || "owner";
|
|
69
|
+
if (parseRoleList(input.role).includes(creatorRole)) return invitations;
|
|
70
|
+
return invitations.filter((invitation) => !parseRoleList(invitation.role).includes(creatorRole));
|
|
71
|
+
};
|
|
33
72
|
//#endregion
|
|
34
|
-
export { hasPermission };
|
|
73
|
+
export { canViewInvitations, hasPermission, withoutCreatorRoleInvitations };
|
|
@@ -2,5 +2,6 @@ import { OrganizationOptions } from "./types.mjs";
|
|
|
2
2
|
import { InferInvitation, InferMember, InferOrganization, InferOrganizationRolesFromOption, InferOrganizationZodRolesFromOption, InferTeam, Invitation, InvitationInput, InvitationStatus, Member, MemberInput, Organization, OrganizationInput, OrganizationRole, OrganizationSchema, Team, TeamInput, TeamMember, TeamMemberInput, defaultRolesSchema, invitationSchema, invitationStatus, memberSchema, organizationRoleSchema, organizationSchema, roleSchema, teamMemberSchema, teamSchema } from "./schema.mjs";
|
|
3
3
|
import { getOrgAdapter } from "./adapter.mjs";
|
|
4
4
|
import { hasPermission } from "./has-permission.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { parseRoles } from "./roles.mjs";
|
|
6
|
+
import { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, OrganizationCreator, OrganizationEndpoints, OrganizationPlugin, TeamEndpoints, organization } from "./organization.mjs";
|
|
6
7
|
export { DefaultOrganizationPlugin, DynamicAccessControlEndpoints, type InferInvitation, type InferMember, type InferOrganization, type InferOrganizationRolesFromOption, type InferOrganizationZodRolesFromOption, type InferTeam, type Invitation, type InvitationInput, type InvitationStatus, type Member, type MemberInput, type Organization, OrganizationCreator, OrganizationEndpoints, type OrganizationInput, type OrganizationOptions, OrganizationPlugin, type OrganizationRole, type OrganizationSchema, type Team, TeamEndpoints, type TeamInput, type TeamMember, type TeamMemberInput, type defaultRolesSchema, getOrgAdapter, hasPermission, type invitationSchema, type invitationStatus, type memberSchema, organization, type organizationRoleSchema, type organizationSchema, parseRoles, type roleSchema, type teamMemberSchema, type teamSchema };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import { parseRoles } from "./roles.mjs";
|
|
1
2
|
import { getOrgAdapter } from "./adapter.mjs";
|
|
2
3
|
import { hasPermission } from "./has-permission.mjs";
|
|
3
|
-
import { organization
|
|
4
|
+
import { organization } from "./organization.mjs";
|
|
4
5
|
export { getOrgAdapter, hasPermission, organization, parseRoles };
|
|
@@ -10,6 +10,7 @@ import { addMember, getActiveMember, getActiveMemberRole, leaveOrganization, lis
|
|
|
10
10
|
import { checkOrganizationSlug, createOrganization, deleteOrganization, getFullOrganization, getOrganization, listOrganizations, setActiveOrganization, updateOrganization } from "./routes/crud-org.mjs";
|
|
11
11
|
import { addTeamMember, createTeam, listOrganizationTeams, listTeamMembers, listUserTeams, removeTeam, removeTeamMember, setActiveTeam, updateTeam } from "./routes/crud-team.mjs";
|
|
12
12
|
import { hasPermission } from "./has-permission.mjs";
|
|
13
|
+
import { parseRoles } from "./roles.mjs";
|
|
13
14
|
import * as z from "zod";
|
|
14
15
|
//#region src/plugins/organization/organization.d.ts
|
|
15
16
|
declare module "@better-auth/core" {
|
|
@@ -51,7 +52,6 @@ export type DefaultOrganizationPlugin<Options extends OrganizationOptions> = {
|
|
|
51
52
|
export interface OrganizationCreator {
|
|
52
53
|
<Options extends OrganizationOptions>(options?: Options | undefined): DefaultOrganizationPlugin<Options>;
|
|
53
54
|
}
|
|
54
|
-
export declare function parseRoles(roles: string | string[]): string;
|
|
55
55
|
export type DynamicAccessControlEndpoints<O extends OrganizationOptions> = {
|
|
56
56
|
createOrgRole: ReturnType<typeof createOrgRole<O>>;
|
|
57
57
|
deleteOrgRole: ReturnType<typeof deleteOrgRole<O>>;
|
|
@@ -417,4 +417,4 @@ export declare function organization<O extends OrganizationOptions & {
|
|
|
417
417
|
};
|
|
418
418
|
export declare function organization<O extends OrganizationOptions>(options?: O | undefined): DefaultOrganizationPlugin<O>;
|
|
419
419
|
//#endregion
|
|
420
|
-
export { type OrganizationOptions, hasPermission };
|
|
420
|
+
export { type OrganizationOptions, hasPermission, parseRoles };
|
|
@@ -2,6 +2,7 @@ import { getSessionFromCtx } from "../../api/routes/session.mjs";
|
|
|
2
2
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
3
3
|
import { defaultRoles } from "./access/statement.mjs";
|
|
4
4
|
import { ORGANIZATION_ERROR_CODES } from "./error-codes.mjs";
|
|
5
|
+
import { parseRoles } from "./roles.mjs";
|
|
5
6
|
import { getOrgAdapter } from "./adapter.mjs";
|
|
6
7
|
import { shimContext } from "../../utils/shim.mjs";
|
|
7
8
|
import { orgSessionMiddleware } from "./call.mjs";
|
|
@@ -15,9 +16,6 @@ import { APIError } from "@better-auth/core/error";
|
|
|
15
16
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
16
17
|
import * as z from "zod";
|
|
17
18
|
//#region src/plugins/organization/organization.ts
|
|
18
|
-
function parseRoles(roles) {
|
|
19
|
-
return Array.isArray(roles) ? roles.join(",") : roles;
|
|
20
|
-
}
|
|
21
19
|
const createHasPermissionBodySchema = z.object({ organizationId: z.string().optional() }).and(z.xor([z.object({ permission: z.record(z.string(), z.array(z.string())) }), z.object({ permissions: z.record(z.string(), z.array(z.string())) })]));
|
|
22
20
|
const createHasPermission = (options) => {
|
|
23
21
|
return createAuthEndpoint("/organization/has-permission", {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { parseRoleList } from "./roles.mjs";
|
|
1
2
|
//#region src/plugins/organization/permission.ts
|
|
2
3
|
const hasPermissionFn = (input, acRoles) => {
|
|
3
4
|
if (!input.permissions) return false;
|
|
4
|
-
const roles = input.role
|
|
5
|
+
const roles = parseRoleList(input.role);
|
|
5
6
|
const creatorRole = input.options.creatorRole || "owner";
|
|
6
7
|
const isCreator = roles.includes(creatorRole);
|
|
7
8
|
const allowCreatorsAllPermissions = input.allowCreatorAllPermissions || false;
|
|
@@ -9,6 +10,5 @@ const hasPermissionFn = (input, acRoles) => {
|
|
|
9
10
|
for (const role of roles) if ((acRoles[role]?.authorize(input.permissions))?.success) return true;
|
|
10
11
|
return false;
|
|
11
12
|
};
|
|
12
|
-
const cacheAllRoles = /* @__PURE__ */ new Map();
|
|
13
13
|
//#endregion
|
|
14
|
-
export {
|
|
14
|
+
export { hasPermissionFn };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/plugins/organization/roles.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes a role value into the comma separated string that is stored in
|
|
4
|
+
* the database. Whitespace around each role name is removed and empty entries
|
|
5
|
+
* are dropped.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseRoles(roles: string | string[]): string;
|
|
8
|
+
//#endregion
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/plugins/organization/roles.ts
|
|
2
|
+
/**
|
|
3
|
+
* Splits a role value into a list of trimmed, non-empty role names.
|
|
4
|
+
*
|
|
5
|
+
* Roles are stored as a comma separated string (`"admin,owner"`), and may be
|
|
6
|
+
* submitted either as that string or as an array. Any whitespace around a
|
|
7
|
+
* role name is removed so that `"admin, owner"`, `["admin", " owner"]` and
|
|
8
|
+
* `"admin,owner"` all resolve to the same roles.
|
|
9
|
+
*
|
|
10
|
+
* Every role comparison and every write of a role column must go through this
|
|
11
|
+
* helper (or `parseRoles`), so a padded role name can never be stored in a
|
|
12
|
+
* form that one check matches and another does not.
|
|
13
|
+
*/
|
|
14
|
+
function parseRoleList(roles) {
|
|
15
|
+
return (Array.isArray(roles) ? roles : [roles]).flatMap((role) => role.split(",")).map((role) => role.trim()).filter(Boolean);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Normalizes a role value into the comma separated string that is stored in
|
|
19
|
+
* the database. Whitespace around each role name is removed and empty entries
|
|
20
|
+
* are dropped.
|
|
21
|
+
*/
|
|
22
|
+
function parseRoles(roles) {
|
|
23
|
+
return parseRoleList(roles).join(",");
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { parseRoleList, parseRoles };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { toZodSchema } from "../../../db/to-zod.mjs";
|
|
2
2
|
import { ORGANIZATION_ERROR_CODES } from "../error-codes.mjs";
|
|
3
|
+
import { parseRoleList } from "../roles.mjs";
|
|
3
4
|
import { orgSessionMiddleware } from "../call.mjs";
|
|
4
5
|
import { hasPermission } from "../has-permission.mjs";
|
|
5
6
|
import { APIError } from "@better-auth/core/error";
|
|
@@ -260,7 +261,7 @@ const deleteOrgRole = (options) => {
|
|
|
260
261
|
operator: "contains"
|
|
261
262
|
}]
|
|
262
263
|
})).find((member) => {
|
|
263
|
-
return member.role
|
|
264
|
+
return parseRoleList(member.role).includes(roleToDelete);
|
|
264
265
|
})) {
|
|
265
266
|
ctx.context.logger.error(`[Dynamic Access Control] Cannot delete a role that is assigned to members.`, {
|
|
266
267
|
role: existingRoleInDB.role,
|
|
@@ -4,10 +4,10 @@ import { toZodSchema } from "../../../db/to-zod.mjs";
|
|
|
4
4
|
import { getSessionFromCtx } from "../../../api/routes/session.mjs";
|
|
5
5
|
import { defaultRoles } from "../access/statement.mjs";
|
|
6
6
|
import { ORGANIZATION_ERROR_CODES } from "../error-codes.mjs";
|
|
7
|
+
import { parseRoleList, parseRoles } from "../roles.mjs";
|
|
7
8
|
import { getOrgAdapter } from "../adapter.mjs";
|
|
8
9
|
import { orgMiddleware, orgSessionMiddleware } from "../call.mjs";
|
|
9
|
-
import { hasPermission } from "../has-permission.mjs";
|
|
10
|
-
import { parseRoles } from "../organization.mjs";
|
|
10
|
+
import { canViewInvitations, hasPermission, withoutCreatorRoleInvitations } from "../has-permission.mjs";
|
|
11
11
|
import { runWithTransaction } from "@better-auth/core/context";
|
|
12
12
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
13
13
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
@@ -99,8 +99,9 @@ const createInvitation = (option) => {
|
|
|
99
99
|
organizationId
|
|
100
100
|
}, ctx)) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION);
|
|
101
101
|
const creatorRole = ctx.context.orgOptions.creatorRole || "owner";
|
|
102
|
-
const
|
|
103
|
-
|
|
102
|
+
const rolesArray = parseRoleList(ctx.body.role);
|
|
103
|
+
if (rolesArray.length === 0) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND);
|
|
104
|
+
const roles = parseRoles(rolesArray);
|
|
104
105
|
const defaults = Object.keys(defaultRoles);
|
|
105
106
|
const customRoles = Object.keys(ctx.context.orgOptions.roles || {});
|
|
106
107
|
const validStaticRoles = /* @__PURE__ */ new Set([...defaults, ...customRoles]);
|
|
@@ -122,7 +123,7 @@ const createInvitation = (option) => {
|
|
|
122
123
|
if (stillInvalid.length > 0) throw new APIError("BAD_REQUEST", { message: `${ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND}: ${stillInvalid.join(", ")}` });
|
|
123
124
|
} else throw new APIError("BAD_REQUEST", { message: `${ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND}: ${unknownRoles.join(", ")}` });
|
|
124
125
|
}
|
|
125
|
-
if (!member.role
|
|
126
|
+
if (!parseRoleList(member.role).includes(creatorRole) && rolesArray.includes(creatorRole)) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE);
|
|
126
127
|
if (await adapter.findMemberByEmail({
|
|
127
128
|
email,
|
|
128
129
|
organizationId
|
|
@@ -134,8 +135,9 @@ const createInvitation = (option) => {
|
|
|
134
135
|
if (alreadyInvited.length && !ctx.body.resend && !ctx.context.orgOptions.cancelPendingInvitationsOnReInvite) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION);
|
|
135
136
|
const organization = await adapter.findOrganizationById(organizationId);
|
|
136
137
|
if (!organization) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND);
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
const existingInvitation = alreadyInvited[0];
|
|
139
|
+
if (existingInvitation && ctx.body.resend) {
|
|
140
|
+
if (!parseRoleList(member.role).includes(creatorRole) && parseRoleList(existingInvitation.role).includes(creatorRole)) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE);
|
|
139
141
|
const newExpiresAt = getDate(ctx.context.orgOptions.invitationExpiresIn || 172800, "sec");
|
|
140
142
|
await ctx.context.adapter.update({
|
|
141
143
|
model: "invitation",
|
|
@@ -327,7 +329,7 @@ const acceptInvitation = (options) => createAuthEndpoint("/organization/accept-i
|
|
|
327
329
|
const createdMember = await adapter.createMember({
|
|
328
330
|
organizationId: acceptedI.organizationId,
|
|
329
331
|
userId: session.user.id,
|
|
330
|
-
role: acceptedI.role,
|
|
332
|
+
role: parseRoles(acceptedI.role),
|
|
331
333
|
createdAt: /* @__PURE__ */ new Date()
|
|
332
334
|
});
|
|
333
335
|
await adapter.setActiveOrganization(session.session.token, acceptedI.organizationId, ctx);
|
|
@@ -536,12 +538,21 @@ const listInvitations = (options) => createAuthEndpoint("/organization/list-invi
|
|
|
536
538
|
const orgId = ctx.query?.organizationId || session.session.activeOrganizationId;
|
|
537
539
|
if (!orgId) throw APIError.fromStatus("BAD_REQUEST", { message: "Organization ID is required" });
|
|
538
540
|
const adapter = getOrgAdapter(ctx.context, options);
|
|
539
|
-
|
|
541
|
+
const isMember = await adapter.findMemberByOrgId({
|
|
540
542
|
userId: session.user.id,
|
|
541
543
|
organizationId: orgId
|
|
542
|
-
})
|
|
544
|
+
});
|
|
545
|
+
if (!isMember) throw APIError.fromStatus("FORBIDDEN", { message: "You are not a member of this organization" });
|
|
546
|
+
if (!await canViewInvitations({
|
|
547
|
+
role: isMember.role,
|
|
548
|
+
options: ctx.context.orgOptions,
|
|
549
|
+
organizationId: orgId
|
|
550
|
+
}, ctx)) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_INVITATIONS);
|
|
543
551
|
const invitations = await adapter.listInvitations({ organizationId: orgId });
|
|
544
|
-
return ctx.json(invitations
|
|
552
|
+
return ctx.json(withoutCreatorRoleInvitations(invitations, {
|
|
553
|
+
role: isMember.role,
|
|
554
|
+
options: ctx.context.orgOptions
|
|
555
|
+
}));
|
|
545
556
|
});
|
|
546
557
|
/**
|
|
547
558
|
* List all invitations a user has received
|