@gatwech/auth 1.0.0-beta.18
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/README.md +31 -0
- package/dist/access-BO2g7QVe.mjs +45 -0
- package/dist/access-C6O_osEz.mjs +79 -0
- package/dist/access-DT1f6l5k.mjs +51 -0
- package/dist/adapter-factory-BZ2Qiqpn.mjs +640 -0
- package/dist/adapters/drizzle-adapter/index.d.mts +47 -0
- package/dist/adapters/drizzle-adapter/index.mjs +208 -0
- package/dist/adapters/index.d.mts +132 -0
- package/dist/adapters/index.mjs +18 -0
- package/dist/adapters/kysely-adapter/index.d.mts +47 -0
- package/dist/adapters/kysely-adapter/index.mjs +11 -0
- package/dist/adapters/memory-adapter/index.d.mts +14 -0
- package/dist/adapters/memory-adapter/index.mjs +154 -0
- package/dist/adapters/mongodb-adapter/index.d.mts +35 -0
- package/dist/adapters/mongodb-adapter/index.mjs +315 -0
- package/dist/adapters/prisma-adapter/index.d.mts +34 -0
- package/dist/adapters/prisma-adapter/index.mjs +159 -0
- package/dist/adapters/test.d.mts +81 -0
- package/dist/adapters/test.mjs +793 -0
- package/dist/admin-Bcq53Nwj.mjs +743 -0
- package/dist/admin-DtfO1PLg.d.mts +1689 -0
- package/dist/anonymous-CRt1imBW.mjs +107 -0
- package/dist/api/index.d.mts +6 -0
- package/dist/api/index.mjs +17 -0
- package/dist/api-0pR_I-Nx.mjs +3139 -0
- package/dist/auth-CBUoBX6o.mjs +233 -0
- package/dist/bearer-Djf_awfo.mjs +63 -0
- package/dist/bun-sqlite-dialect-CeNvMaZz.mjs +155 -0
- package/dist/captcha-BqaMxlKC.mjs +178 -0
- package/dist/chunk-BK_LfMhE.mjs +40 -0
- package/dist/client/index.d.mts +186 -0
- package/dist/client/index.mjs +7 -0
- package/dist/client/plugins/index.d.mts +949 -0
- package/dist/client/plugins/index.mjs +539 -0
- package/dist/client/react/index.d.mts +179 -0
- package/dist/client/react/index.mjs +69 -0
- package/dist/client-BOX93C3d.d.mts +1250 -0
- package/dist/client-CNY0tn9g.d.mts +628 -0
- package/dist/client-CygJhqeB.mjs +29 -0
- package/dist/client-WiN43tAS.mjs +17 -0
- package/dist/client-aExCkfwm.mjs +30 -0
- package/dist/cookies/index.d.mts +7 -0
- package/dist/cookies/index.mjs +7 -0
- package/dist/cookies-cJObLwn1.mjs +597 -0
- package/dist/crypto/index.d.mts +44 -0
- package/dist/crypto/index.mjs +4 -0
- package/dist/crypto-Bb42eMrH.mjs +70 -0
- package/dist/custom-session-L3t7ecsd.mjs +67 -0
- package/dist/date-Cx2yCiC4.mjs +115 -0
- package/dist/db/index.d.mts +7 -0
- package/dist/db/index.mjs +15 -0
- package/dist/db-BrstmDhc.mjs +1124 -0
- package/dist/device-authorization-V4s3h4Mj.mjs +557 -0
- package/dist/dialect-CeddeGHf.mjs +72 -0
- package/dist/email-otp-BtDHSw14.mjs +592 -0
- package/dist/esm-C-QicDNz.mjs +4090 -0
- package/dist/generic-oauth-CL5X27ZI.mjs +445 -0
- package/dist/get-request-ip-DSjrEpGo.mjs +27 -0
- package/dist/get-tables-BQP7YW9c.mjs +249 -0
- package/dist/has-permission-BpAr7uCn.mjs +14 -0
- package/dist/haveibeenpwned-cMpJvmzI.mjs +43 -0
- package/dist/helper-tmxW3BHj.d.mts +21 -0
- package/dist/index-34ONrP8l.d.mts +503 -0
- package/dist/index-65G5U5DB-DSdjzreF.d.mts +2543 -0
- package/dist/index-B-XKqFvY.d.mts +30 -0
- package/dist/index-B3n8e37d.d.mts +165 -0
- package/dist/index-BBm8nQ9Q.d.mts +42 -0
- package/dist/index-BHoQlf7a.d.mts +7699 -0
- package/dist/index-BYMI9r_d.d.mts +257 -0
- package/dist/index-BqhKCbPH.d.mts +36 -0
- package/dist/index-Buhn3hkR.d.mts +433 -0
- package/dist/index-C-u1h_WX.d.mts +174 -0
- package/dist/index-C7fQuk-V.d.mts +41 -0
- package/dist/index-CM-CFWwr.d.mts +1919 -0
- package/dist/index-CuFzhoS6.d.mts +359 -0
- package/dist/index-Cxi7TbLE.d.mts +11347 -0
- package/dist/index-DB7t8whN.d.mts +1 -0
- package/dist/index-DKNNV1kb.d.mts +626 -0
- package/dist/index-DR0xXno9.d.mts +113 -0
- package/dist/index-D_k662QG.d.mts +833 -0
- package/dist/index-DaBcikv9.d.mts +118 -0
- package/dist/index-DlaMq1nb.d.mts +955 -0
- package/dist/index-DoXZYQpq.d.mts +290 -0
- package/dist/index-G6V0Z_Mn.d.mts +125 -0
- package/dist/index-z0KSvE5i.d.mts +246 -0
- package/dist/index-zjjNBRY2.d.mts +95 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +33 -0
- package/dist/integrations/next-js.d.mts +21 -0
- package/dist/integrations/next-js.mjs +62 -0
- package/dist/integrations/node.d.mts +15 -0
- package/dist/integrations/node.mjs +15 -0
- package/dist/integrations/react-start.d.mts +15 -0
- package/dist/integrations/react-start.mjs +46 -0
- package/dist/json-D0TBLy4q.mjs +24 -0
- package/dist/jwt-CmxZAGe2.mjs +94 -0
- package/dist/jwt-lHV4QHpD.mjs +387 -0
- package/dist/kysely-adapter-DpzYO8Yf.mjs +181 -0
- package/dist/magic-link-Bk-ueorR.mjs +176 -0
- package/dist/misc-beA1w3XV.mjs +7 -0
- package/dist/multi-session-DRrLKrg9.mjs +144 -0
- package/dist/node-sqlite-dialect-BcKxBQnm.mjs +155 -0
- package/dist/oauth-proxy-D1sWh9B_.mjs +133 -0
- package/dist/oauth2/index.d.mts +60 -0
- package/dist/oauth2/index.mjs +19 -0
- package/dist/one-tap-DMswkTEc.mjs +113 -0
- package/dist/organization-BArHO_5v.mjs +3857 -0
- package/dist/parser-DcrIEuXE.mjs +73 -0
- package/dist/passkey-CcD4CnhM.mjs +558 -0
- package/dist/password-sVC4nePO.mjs +25 -0
- package/dist/permission-DLYUF3cW.mjs +15 -0
- package/dist/phone-number-rHDIlh-2.mjs +485 -0
- package/dist/plugin-helper-4L-lzsjZ.mjs +16 -0
- package/dist/plugins/access/index.d.mts +3 -0
- package/dist/plugins/access/index.mjs +3 -0
- package/dist/plugins/admin/access/index.d.mts +116 -0
- package/dist/plugins/admin/access/index.mjs +4 -0
- package/dist/plugins/admin/index.d.mts +9 -0
- package/dist/plugins/admin/index.mjs +22 -0
- package/dist/plugins/anonymous/index.d.mts +7 -0
- package/dist/plugins/anonymous/index.mjs +18 -0
- package/dist/plugins/bearer/index.d.mts +2 -0
- package/dist/plugins/bearer/index.mjs +8 -0
- package/dist/plugins/captcha/index.d.mts +2 -0
- package/dist/plugins/captcha/index.mjs +4 -0
- package/dist/plugins/custom-session/index.d.mts +7 -0
- package/dist/plugins/custom-session/index.mjs +19 -0
- package/dist/plugins/device-authorization/index.d.mts +4 -0
- package/dist/plugins/device-authorization/index.mjs +18 -0
- package/dist/plugins/email-otp/index.d.mts +3 -0
- package/dist/plugins/email-otp/index.mjs +19 -0
- package/dist/plugins/generic-oauth/index.d.mts +7 -0
- package/dist/plugins/generic-oauth/index.mjs +18 -0
- package/dist/plugins/haveibeenpwned/index.d.mts +2 -0
- package/dist/plugins/haveibeenpwned/index.mjs +18 -0
- package/dist/plugins/index.d.mts +139 -0
- package/dist/plugins/index.mjs +47 -0
- package/dist/plugins/jwt/index.d.mts +7 -0
- package/dist/plugins/jwt/index.mjs +18 -0
- package/dist/plugins/magic-link/index.d.mts +2 -0
- package/dist/plugins/magic-link/index.mjs +18 -0
- package/dist/plugins/multi-session/index.d.mts +3 -0
- package/dist/plugins/multi-session/index.mjs +18 -0
- package/dist/plugins/oauth-proxy/index.d.mts +2 -0
- package/dist/plugins/oauth-proxy/index.mjs +18 -0
- package/dist/plugins/one-tap/index.d.mts +2 -0
- package/dist/plugins/one-tap/index.mjs +18 -0
- package/dist/plugins/one-time-token/index.d.mts +7 -0
- package/dist/plugins/one-time-token/index.mjs +47 -0
- package/dist/plugins/organization/access/index.d.mts +4 -0
- package/dist/plugins/organization/access/index.mjs +4 -0
- package/dist/plugins/organization/index.d.mts +9 -0
- package/dist/plugins/organization/index.mjs +22 -0
- package/dist/plugins/passkey/index.d.mts +7 -0
- package/dist/plugins/passkey/index.mjs +18 -0
- package/dist/plugins/phone-number/index.d.mts +7 -0
- package/dist/plugins/phone-number/index.mjs +18 -0
- package/dist/plugins/two-factor/index.d.mts +7 -0
- package/dist/plugins/two-factor/index.mjs +20 -0
- package/dist/plugins/username/index.d.mts +4 -0
- package/dist/plugins/username/index.mjs +18 -0
- package/dist/plugins-3jfNste3.mjs +1544 -0
- package/dist/plugins-hFZT3hmK.d.mts +14 -0
- package/dist/promise-8rrEHRXd.mjs +23154 -0
- package/dist/proxy-CL9PZXJA.mjs +441 -0
- package/dist/session-Dgp133Yt.mjs +413 -0
- package/dist/social-providers/index.d.mts +1 -0
- package/dist/social-providers/index.mjs +3 -0
- package/dist/src/adapters/adapter-factory/index.d.ts +10 -0
- package/dist/src/adapters/adapter-factory/types.d.ts +124 -0
- package/dist/src/adapters/create-test-suite.d.ts +97 -0
- package/dist/src/adapters/drizzle-adapter/drizzle-adapter.d.ts +43 -0
- package/dist/src/adapters/drizzle-adapter/index.d.ts +1 -0
- package/dist/src/adapters/drizzle-adapter/test/generate-schema.d.ts +12 -0
- package/dist/src/adapters/index.d.ts +21 -0
- package/dist/src/adapters/kysely-adapter/bun-sqlite-dialect.d.ts +58 -0
- package/dist/src/adapters/kysely-adapter/dialect.d.ts +13 -0
- package/dist/src/adapters/kysely-adapter/index.d.ts +3 -0
- package/dist/src/adapters/kysely-adapter/kysely-adapter.d.ts +32 -0
- package/dist/src/adapters/kysely-adapter/node-sqlite-dialect.d.ts +58 -0
- package/dist/src/adapters/kysely-adapter/types.d.ts +1 -0
- package/dist/src/adapters/memory-adapter/index.d.ts +1 -0
- package/dist/src/adapters/memory-adapter/memory-adapter.d.ts +9 -0
- package/dist/src/adapters/mongodb-adapter/index.d.ts +1 -0
- package/dist/src/adapters/mongodb-adapter/mongodb-adapter.d.ts +31 -0
- package/dist/src/adapters/prisma-adapter/index.d.ts +1 -0
- package/dist/src/adapters/prisma-adapter/prisma-adapter.d.ts +32 -0
- package/dist/src/adapters/prisma-adapter/test/auth.d.ts +3 -0
- package/dist/src/adapters/prisma-adapter/test/generate-auth-config.d.ts +2 -0
- package/dist/src/adapters/prisma-adapter/test/generate-prisma-schema.d.ts +3 -0
- package/dist/src/adapters/prisma-adapter/test/get-prisma-client.d.ts +7 -0
- package/dist/src/adapters/prisma-adapter/test/push-prisma-schema.d.ts +1 -0
- package/dist/src/adapters/test-adapter.d.ts +61 -0
- package/dist/src/adapters/test.d.ts +78 -0
- package/dist/src/adapters/tests/auth-flow.d.ts +22 -0
- package/dist/src/adapters/tests/index.d.ts +5 -0
- package/dist/src/adapters/tests/normal.d.ts +87 -0
- package/dist/src/adapters/tests/number-id.d.ts +17 -0
- package/dist/src/adapters/tests/performance.d.ts +24 -0
- package/dist/src/adapters/tests/transactions.d.ts +20 -0
- package/dist/src/adapters/utils.d.ts +3 -0
- package/dist/src/api/index.d.ts +7150 -0
- package/dist/src/api/middlewares/index.d.ts +2 -0
- package/dist/src/api/middlewares/oauth.d.ts +15 -0
- package/dist/src/api/middlewares/origin-check.d.ts +8 -0
- package/dist/src/api/rate-limiter/index.d.ts +13 -0
- package/dist/src/api/routes/account.d.ts +623 -0
- package/dist/src/api/routes/callback.d.ts +68 -0
- package/dist/src/api/routes/email-verification.d.ts +266 -0
- package/dist/src/api/routes/error.d.ts +51 -0
- package/dist/src/api/routes/index.d.ts +11 -0
- package/dist/src/api/routes/ok.d.ts +61 -0
- package/dist/src/api/routes/reset-password.d.ts +203 -0
- package/dist/src/api/routes/session.d.ts +586 -0
- package/dist/src/api/routes/sign-in.d.ts +298 -0
- package/dist/src/api/routes/sign-out.d.ts +59 -0
- package/dist/src/api/routes/sign-up.d.ts +625 -0
- package/dist/src/api/routes/update-user.d.ts +862 -0
- package/dist/src/api/to-auth-endpoints.d.ts +3 -0
- package/dist/src/auth.d.ts +25 -0
- package/dist/src/client/broadcast-channel.d.ts +12 -0
- package/dist/src/client/config.d.ts +85 -0
- package/dist/src/client/fetch-plugins.d.ts +14 -0
- package/dist/src/client/index.d.ts +15 -0
- package/dist/src/client/parser.d.ts +12 -0
- package/dist/src/client/path-to-object.d.ts +63 -0
- package/dist/src/client/plugins/index.d.ts +21 -0
- package/dist/src/client/plugins/infer-plugin.d.ts +11 -0
- package/dist/src/client/proxy.d.ts +8 -0
- package/dist/src/client/query.d.ts +16 -0
- package/dist/src/client/react/index.d.ts +130 -0
- package/dist/src/client/react/react-store.d.ts +43 -0
- package/dist/src/client/session-atom.d.ts +18 -0
- package/dist/src/client/session-refresh.d.ts +17 -0
- package/dist/src/client/test-plugin.d.ts +147 -0
- package/dist/src/client/types.d.ts +49 -0
- package/dist/src/client/vanilla.d.ts +124 -0
- package/dist/src/cookies/check-cookies.d.ts +4 -0
- package/dist/src/cookies/cookie-utils.d.ts +16 -0
- package/dist/src/cookies/index.d.ts +56 -0
- package/dist/src/cookies/session-store.d.ts +43 -0
- package/dist/src/crypto/buffer.d.ts +4 -0
- package/dist/src/crypto/index.d.ts +14 -0
- package/dist/src/crypto/jwt.d.ts +4 -0
- package/dist/src/crypto/password.d.ts +5 -0
- package/dist/src/crypto/random.d.ts +1 -0
- package/dist/src/db/field.d.ts +97 -0
- package/dist/src/db/get-migration.d.ts +18 -0
- package/dist/src/db/get-schema.d.ts +6 -0
- package/dist/src/db/get-tables.d.ts +3 -0
- package/dist/src/db/index.d.ts +10 -0
- package/dist/src/db/internal-adapter.d.ts +9 -0
- package/dist/src/db/schema.d.ts +76 -0
- package/dist/src/db/to-zod.d.ts +31 -0
- package/dist/src/db/utils.d.ts +6 -0
- package/dist/src/db/with-hooks.d.ts +27 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/init.d.ts +2 -0
- package/dist/src/integrations/next-js.d.ts +15 -0
- package/dist/src/integrations/node.d.ts +6 -0
- package/dist/src/integrations/react-start.d.ts +9 -0
- package/dist/src/oauth2/index.d.ts +4 -0
- package/dist/src/oauth2/link-account.d.ts +41 -0
- package/dist/src/oauth2/state.d.ts +21 -0
- package/dist/src/oauth2/utils.d.ts +3 -0
- package/dist/src/plugins/access/access.d.ts +25 -0
- package/dist/src/plugins/access/index.d.ts +2 -0
- package/dist/src/plugins/access/types.d.ts +14 -0
- package/dist/src/plugins/additional-fields/client.d.ts +91 -0
- package/dist/src/plugins/admin/access/index.d.ts +1 -0
- package/dist/src/plugins/admin/access/statement.d.ts +110 -0
- package/dist/src/plugins/admin/admin.d.ts +1570 -0
- package/dist/src/plugins/admin/client.d.ts +62 -0
- package/dist/src/plugins/admin/error-codes.d.ts +21 -0
- package/dist/src/plugins/admin/has-permission.d.ts +21 -0
- package/dist/src/plugins/admin/index.d.ts +2 -0
- package/dist/src/plugins/admin/schema.d.ts +36 -0
- package/dist/src/plugins/admin/types.d.ts +77 -0
- package/dist/src/plugins/anonymous/client.d.ts +8 -0
- package/dist/src/plugins/anonymous/index.d.ts +159 -0
- package/dist/src/plugins/api-key/client.d.ts +11 -0
- package/dist/src/plugins/api-key/index.d.ts +1431 -0
- package/dist/src/plugins/api-key/rate-limit.d.ts +20 -0
- package/dist/src/plugins/api-key/routes/create-api-key.d.ts +244 -0
- package/dist/src/plugins/api-key/routes/delete-all-expired-api-keys.d.ts +45 -0
- package/dist/src/plugins/api-key/routes/delete-api-key.d.ts +112 -0
- package/dist/src/plugins/api-key/routes/get-api-key.d.ts +234 -0
- package/dist/src/plugins/api-key/routes/index.d.ts +1136 -0
- package/dist/src/plugins/api-key/routes/list-api-keys.d.ts +231 -0
- package/dist/src/plugins/api-key/routes/update-api-key.d.ts +235 -0
- package/dist/src/plugins/api-key/routes/verify-api-key.d.ts +80 -0
- package/dist/src/plugins/api-key/schema.d.ts +188 -0
- package/dist/src/plugins/api-key/types.d.ts +285 -0
- package/dist/src/plugins/bearer/index.d.ts +31 -0
- package/dist/src/plugins/captcha/constants.d.ts +9 -0
- package/dist/src/plugins/captcha/error-codes.d.ts +9 -0
- package/dist/src/plugins/captcha/index.d.ts +7 -0
- package/dist/src/plugins/captcha/types.d.ts +23 -0
- package/dist/src/plugins/captcha/utils.d.ts +1 -0
- package/dist/src/plugins/captcha/verify-handlers/captchafox.d.ts +11 -0
- package/dist/src/plugins/captcha/verify-handlers/cloudflare-turnstile.d.ts +10 -0
- package/dist/src/plugins/captcha/verify-handlers/google-recaptcha.d.ts +11 -0
- package/dist/src/plugins/captcha/verify-handlers/h-captcha.d.ts +11 -0
- package/dist/src/plugins/captcha/verify-handlers/index.d.ts +4 -0
- package/dist/src/plugins/custom-session/client.d.ts +12 -0
- package/dist/src/plugins/custom-session/index.d.ts +89 -0
- package/dist/src/plugins/device-authorization/client.d.ts +12 -0
- package/dist/src/plugins/device-authorization/index.d.ts +568 -0
- package/dist/src/plugins/device-authorization/schema.d.ts +56 -0
- package/dist/src/plugins/email-otp/client.d.ts +9 -0
- package/dist/src/plugins/email-otp/index.d.ts +826 -0
- package/dist/src/plugins/email-otp/utils.d.ts +2 -0
- package/dist/src/plugins/generic-oauth/client.d.ts +5 -0
- package/dist/src/plugins/generic-oauth/index.d.ts +428 -0
- package/dist/src/plugins/haveibeenpwned/index.d.ts +25 -0
- package/dist/src/plugins/index.d.ts +28 -0
- package/dist/src/plugins/jwt/adapter.d.ts +8 -0
- package/dist/src/plugins/jwt/client.d.ts +5 -0
- package/dist/src/plugins/jwt/index.d.ts +276 -0
- package/dist/src/plugins/jwt/schema.d.ts +18 -0
- package/dist/src/plugins/jwt/sign.d.ts +8 -0
- package/dist/src/plugins/jwt/types.d.ts +175 -0
- package/dist/src/plugins/jwt/utils.d.ts +46 -0
- package/dist/src/plugins/last-login-method/client.d.ts +34 -0
- package/dist/src/plugins/last-login-method/index.d.ts +103 -0
- package/dist/src/plugins/magic-link/client.d.ts +5 -0
- package/dist/src/plugins/magic-link/index.d.ts +253 -0
- package/dist/src/plugins/magic-link/utils.d.ts +1 -0
- package/dist/src/plugins/multi-session/client.d.ts +9 -0
- package/dist/src/plugins/multi-session/index.d.ts +284 -0
- package/dist/src/plugins/oauth-proxy/index.d.ts +119 -0
- package/dist/src/plugins/one-tap/client.d.ts +81 -0
- package/dist/src/plugins/one-tap/index.d.ts +114 -0
- package/dist/src/plugins/one-time-token/client.d.ts +5 -0
- package/dist/src/plugins/one-time-token/index.d.ts +170 -0
- package/dist/src/plugins/one-time-token/utils.d.ts +1 -0
- package/dist/src/plugins/organization/access/index.d.ts +1 -0
- package/dist/src/plugins/organization/access/statement.d.ts +241 -0
- package/dist/src/plugins/organization/adapter.d.ts +685 -0
- package/dist/src/plugins/organization/call.d.ts +55 -0
- package/dist/src/plugins/organization/client.d.ts +344 -0
- package/dist/src/plugins/organization/error-codes.d.ts +58 -0
- package/dist/src/plugins/organization/has-permission.d.ts +13 -0
- package/dist/src/plugins/organization/index.d.ts +4 -0
- package/dist/src/plugins/organization/organization.d.ts +412 -0
- package/dist/src/plugins/organization/permission.d.ts +27 -0
- package/dist/src/plugins/organization/routes/crud-access-control.d.ts +701 -0
- package/dist/src/plugins/organization/routes/crud-invites.d.ts +1522 -0
- package/dist/src/plugins/organization/routes/crud-members.d.ts +1220 -0
- package/dist/src/plugins/organization/routes/crud-org.d.ts +1399 -0
- package/dist/src/plugins/organization/routes/crud-team.d.ts +1323 -0
- package/dist/src/plugins/organization/schema.d.ts +366 -0
- package/dist/src/plugins/organization/types.d.ts +737 -0
- package/dist/src/plugins/passkey/client.d.ts +190 -0
- package/dist/src/plugins/passkey/error-codes.d.ts +9 -0
- package/dist/src/plugins/passkey/index.d.ts +838 -0
- package/dist/src/plugins/passkey/schema.d.ts +50 -0
- package/dist/src/plugins/passkey/types.d.ts +69 -0
- package/dist/src/plugins/passkey/utils.d.ts +2 -0
- package/dist/src/plugins/phone-number/client.d.ts +9 -0
- package/dist/src/plugins/phone-number/index.d.ts +622 -0
- package/dist/src/plugins/phone-number/phone-number-error.d.ts +10 -0
- package/dist/src/plugins/two-factor/backup-codes/index.d.ts +387 -0
- package/dist/src/plugins/two-factor/client.d.ts +28 -0
- package/dist/src/plugins/two-factor/constant.d.ts +2 -0
- package/dist/src/plugins/two-factor/error-code.d.ts +11 -0
- package/dist/src/plugins/two-factor/index.d.ts +1014 -0
- package/dist/src/plugins/two-factor/otp/index.d.ts +282 -0
- package/dist/src/plugins/two-factor/schema.d.ts +35 -0
- package/dist/src/plugins/two-factor/totp/index.d.ts +298 -0
- package/dist/src/plugins/two-factor/types.d.ts +50 -0
- package/dist/src/plugins/two-factor/utils.d.ts +1 -0
- package/dist/src/plugins/two-factor/verify-two-factor.d.ts +59 -0
- package/dist/src/plugins/username/client.d.ts +5 -0
- package/dist/src/plugins/username/error-codes.d.ts +10 -0
- package/dist/src/plugins/username/index.d.ts +313 -0
- package/dist/src/plugins/username/schema.d.ts +27 -0
- package/dist/src/social-providers/index.d.ts +1 -0
- package/dist/src/test-utils/headers.d.ts +5 -0
- package/dist/src/test-utils/index.d.ts +2 -0
- package/dist/src/test-utils/test-instance.d.ts +17849 -0
- package/dist/src/types/adapter.d.ts +21 -0
- package/dist/src/types/api.d.ts +29 -0
- package/dist/src/types/helper.d.ts +32 -0
- package/dist/src/types/index.d.ts +7 -0
- package/dist/src/types/models.d.ts +13 -0
- package/dist/src/types/plugins.d.ts +14 -0
- package/dist/src/utils/await-object.d.ts +3 -0
- package/dist/src/utils/boolean.d.ts +1 -0
- package/dist/src/utils/clone.d.ts +1 -0
- package/dist/src/utils/constants.d.ts +1 -0
- package/dist/src/utils/date.d.ts +1 -0
- package/dist/src/utils/ensure-utc.d.ts +1 -0
- package/dist/src/utils/get-request-ip.d.ts +2 -0
- package/dist/src/utils/hashing.d.ts +6 -0
- package/dist/src/utils/hide-metadata.d.ts +3 -0
- package/dist/src/utils/id.d.ts +1 -0
- package/dist/src/utils/import-util.d.ts +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/is-atom.d.ts +2 -0
- package/dist/src/utils/is-promise.d.ts +1 -0
- package/dist/src/utils/json.d.ts +1 -0
- package/dist/src/utils/merger.d.ts +1 -0
- package/dist/src/utils/middleware-response.d.ts +8 -0
- package/dist/src/utils/misc.d.ts +1 -0
- package/dist/src/utils/password.d.ts +6 -0
- package/dist/src/utils/plugin-helper.d.ts +5 -0
- package/dist/src/utils/shim.d.ts +3 -0
- package/dist/src/utils/time.d.ts +4 -0
- package/dist/src/utils/url.d.ts +4 -0
- package/dist/src/utils/wildcard.d.ts +45 -0
- package/dist/test-utils/index.d.mts +17870 -0
- package/dist/test-utils/index.mjs +272 -0
- package/dist/two-factor-ClUXxom2.mjs +914 -0
- package/dist/types/index.d.mts +6 -0
- package/dist/types/index.mjs +3 -0
- package/dist/types-B1OL7hyf.d.mts +4 -0
- package/dist/types-Bjn15Uy6.mjs +1 -0
- package/dist/url-VB2gAbTI.mjs +59 -0
- package/dist/username-DiGklOGl.mjs +278 -0
- package/dist/utils-SOMB4Px5.mjs +132 -0
- package/package.json +447 -0
|
@@ -0,0 +1,3857 @@
|
|
|
1
|
+
import { t as generateId } from "./utils-SOMB4Px5.mjs";
|
|
2
|
+
import { t as APIError$1 } from "./api-0pR_I-Nx.mjs";
|
|
3
|
+
import { c as setSessionCookie } from "./cookies-cJObLwn1.mjs";
|
|
4
|
+
import { t as getDate } from "./date-Cx2yCiC4.mjs";
|
|
5
|
+
import { i as toZodSchema } from "./db-BrstmDhc.mjs";
|
|
6
|
+
import { a as requestOnlySessionMiddleware, r as getSessionFromCtx, u as sessionMiddleware } from "./session-Dgp133Yt.mjs";
|
|
7
|
+
import { n as parser_default } from "./parser-DcrIEuXE.mjs";
|
|
8
|
+
import { r as defaultRoles } from "./access-C6O_osEz.mjs";
|
|
9
|
+
import { n as hasPermissionFn, t as cacheAllRoles } from "./permission-DLYUF3cW.mjs";
|
|
10
|
+
import { getCurrentAdapter } from "@gatwech/core/context";
|
|
11
|
+
import { BASE_ERROR_CODES, PayvoAuthError } from "@gatwech/core/error";
|
|
12
|
+
import { defineErrorCodes } from "@gatwech/core/utils";
|
|
13
|
+
import { APIError } from "better-call";
|
|
14
|
+
import { createAuthEndpoint, createAuthMiddleware } from "@gatwech/core/api";
|
|
15
|
+
import * as z$1 from "zod";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
|
|
18
|
+
//#region src/utils/shim.ts
|
|
19
|
+
const shimContext = (originalObject, newContext) => {
|
|
20
|
+
const shimmedObj = {};
|
|
21
|
+
for (const [key, value] of Object.entries(originalObject)) {
|
|
22
|
+
shimmedObj[key] = (ctx) => {
|
|
23
|
+
return value({
|
|
24
|
+
...ctx,
|
|
25
|
+
context: {
|
|
26
|
+
...newContext,
|
|
27
|
+
...ctx.context
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
shimmedObj[key].path = value.path;
|
|
32
|
+
shimmedObj[key].method = value.method;
|
|
33
|
+
shimmedObj[key].options = value.options;
|
|
34
|
+
shimmedObj[key].headers = value.headers;
|
|
35
|
+
}
|
|
36
|
+
return shimmedObj;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/plugins/organization/adapter.ts
|
|
41
|
+
const getOrgAdapter = (context, options) => {
|
|
42
|
+
const baseAdapter = context.adapter;
|
|
43
|
+
return {
|
|
44
|
+
findOrganizationBySlug: async (slug) => {
|
|
45
|
+
return await (await getCurrentAdapter(baseAdapter)).findOne({
|
|
46
|
+
model: "organization",
|
|
47
|
+
where: [{
|
|
48
|
+
field: "slug",
|
|
49
|
+
value: slug
|
|
50
|
+
}]
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
createOrganization: async (data) => {
|
|
54
|
+
const organization$1 = await (await getCurrentAdapter(baseAdapter)).create({
|
|
55
|
+
model: "organization",
|
|
56
|
+
data: {
|
|
57
|
+
...data.organization,
|
|
58
|
+
metadata: data.organization.metadata ? JSON.stringify(data.organization.metadata) : void 0
|
|
59
|
+
},
|
|
60
|
+
forceAllowId: true
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
...organization$1,
|
|
64
|
+
metadata: organization$1.metadata && typeof organization$1.metadata === "string" ? JSON.parse(organization$1.metadata) : void 0
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
findMemberByEmail: async (data) => {
|
|
68
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
69
|
+
const user = await adapter.findOne({
|
|
70
|
+
model: "user",
|
|
71
|
+
where: [{
|
|
72
|
+
field: "email",
|
|
73
|
+
value: data.email.toLowerCase()
|
|
74
|
+
}]
|
|
75
|
+
});
|
|
76
|
+
if (!user) return null;
|
|
77
|
+
const member = await adapter.findOne({
|
|
78
|
+
model: "member",
|
|
79
|
+
where: [{
|
|
80
|
+
field: "organizationId",
|
|
81
|
+
value: data.organizationId
|
|
82
|
+
}, {
|
|
83
|
+
field: "userId",
|
|
84
|
+
value: user.id
|
|
85
|
+
}]
|
|
86
|
+
});
|
|
87
|
+
if (!member) return null;
|
|
88
|
+
return {
|
|
89
|
+
...member,
|
|
90
|
+
user: {
|
|
91
|
+
id: user.id,
|
|
92
|
+
name: user.name,
|
|
93
|
+
email: user.email,
|
|
94
|
+
image: user.image
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
listMembers: async (data) => {
|
|
99
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
100
|
+
const members = await Promise.all([adapter.findMany({
|
|
101
|
+
model: "member",
|
|
102
|
+
where: [{
|
|
103
|
+
field: "organizationId",
|
|
104
|
+
value: data.organizationId
|
|
105
|
+
}, ...data.filter?.field ? [{
|
|
106
|
+
field: data.filter?.field,
|
|
107
|
+
value: data.filter?.value
|
|
108
|
+
}] : []],
|
|
109
|
+
limit: data.limit || options?.membershipLimit || 100,
|
|
110
|
+
offset: data.offset || 0,
|
|
111
|
+
sortBy: data.sortBy ? {
|
|
112
|
+
field: data.sortBy,
|
|
113
|
+
direction: data.sortOrder || "asc"
|
|
114
|
+
} : void 0
|
|
115
|
+
}), adapter.count({
|
|
116
|
+
model: "member",
|
|
117
|
+
where: [{
|
|
118
|
+
field: "organizationId",
|
|
119
|
+
value: data.organizationId
|
|
120
|
+
}, ...data.filter?.field ? [{
|
|
121
|
+
field: data.filter?.field,
|
|
122
|
+
value: data.filter?.value
|
|
123
|
+
}] : []]
|
|
124
|
+
})]);
|
|
125
|
+
const users = await adapter.findMany({
|
|
126
|
+
model: "user",
|
|
127
|
+
where: [{
|
|
128
|
+
field: "id",
|
|
129
|
+
value: members[0].map((member) => member.userId),
|
|
130
|
+
operator: "in"
|
|
131
|
+
}]
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
members: members[0].map((member) => {
|
|
135
|
+
const user = users.find((user$1) => user$1.id === member.userId);
|
|
136
|
+
if (!user) throw new PayvoAuthError("Unexpected error: User not found for member");
|
|
137
|
+
return {
|
|
138
|
+
...member,
|
|
139
|
+
user: {
|
|
140
|
+
id: user.id,
|
|
141
|
+
name: user.name,
|
|
142
|
+
email: user.email,
|
|
143
|
+
image: user.image
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}),
|
|
147
|
+
total: members[1]
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
findMemberByOrgId: async (data) => {
|
|
151
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
152
|
+
const [member, user] = await Promise.all([await adapter.findOne({
|
|
153
|
+
model: "member",
|
|
154
|
+
where: [{
|
|
155
|
+
field: "userId",
|
|
156
|
+
value: data.userId
|
|
157
|
+
}, {
|
|
158
|
+
field: "organizationId",
|
|
159
|
+
value: data.organizationId
|
|
160
|
+
}]
|
|
161
|
+
}), await adapter.findOne({
|
|
162
|
+
model: "user",
|
|
163
|
+
where: [{
|
|
164
|
+
field: "id",
|
|
165
|
+
value: data.userId
|
|
166
|
+
}]
|
|
167
|
+
})]);
|
|
168
|
+
if (!user || !member) return null;
|
|
169
|
+
return {
|
|
170
|
+
...member,
|
|
171
|
+
user: {
|
|
172
|
+
id: user.id,
|
|
173
|
+
name: user.name,
|
|
174
|
+
email: user.email,
|
|
175
|
+
image: user.image
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
findMemberById: async (memberId) => {
|
|
180
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
181
|
+
const member = await adapter.findOne({
|
|
182
|
+
model: "member",
|
|
183
|
+
where: [{
|
|
184
|
+
field: "id",
|
|
185
|
+
value: memberId
|
|
186
|
+
}]
|
|
187
|
+
});
|
|
188
|
+
if (!member) return null;
|
|
189
|
+
const user = await adapter.findOne({
|
|
190
|
+
model: "user",
|
|
191
|
+
where: [{
|
|
192
|
+
field: "id",
|
|
193
|
+
value: member.userId
|
|
194
|
+
}]
|
|
195
|
+
});
|
|
196
|
+
if (!user) return null;
|
|
197
|
+
return {
|
|
198
|
+
...member,
|
|
199
|
+
user: {
|
|
200
|
+
id: user.id,
|
|
201
|
+
name: user.name,
|
|
202
|
+
email: user.email,
|
|
203
|
+
image: user.image
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
createMember: async (data) => {
|
|
208
|
+
return await (await getCurrentAdapter(baseAdapter)).create({
|
|
209
|
+
model: "member",
|
|
210
|
+
data: {
|
|
211
|
+
...data,
|
|
212
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
updateMember: async (memberId, role) => {
|
|
217
|
+
return await (await getCurrentAdapter(baseAdapter)).update({
|
|
218
|
+
model: "member",
|
|
219
|
+
where: [{
|
|
220
|
+
field: "id",
|
|
221
|
+
value: memberId
|
|
222
|
+
}],
|
|
223
|
+
update: { role }
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
deleteMember: async ({ memberId, organizationId, userId: _userId }) => {
|
|
227
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
228
|
+
let userId;
|
|
229
|
+
if (!_userId) {
|
|
230
|
+
const member$1 = await adapter.findOne({
|
|
231
|
+
model: "member",
|
|
232
|
+
where: [{
|
|
233
|
+
field: "id",
|
|
234
|
+
value: memberId
|
|
235
|
+
}]
|
|
236
|
+
});
|
|
237
|
+
if (!member$1) throw new PayvoAuthError("Member not found");
|
|
238
|
+
userId = member$1.userId;
|
|
239
|
+
} else userId = _userId;
|
|
240
|
+
const member = await adapter.delete({
|
|
241
|
+
model: "member",
|
|
242
|
+
where: [{
|
|
243
|
+
field: "id",
|
|
244
|
+
value: memberId
|
|
245
|
+
}]
|
|
246
|
+
});
|
|
247
|
+
if (options?.teams?.enabled) {
|
|
248
|
+
const teams = await adapter.findMany({
|
|
249
|
+
model: "team",
|
|
250
|
+
where: [{
|
|
251
|
+
field: "organizationId",
|
|
252
|
+
value: organizationId
|
|
253
|
+
}]
|
|
254
|
+
});
|
|
255
|
+
await Promise.all(teams.map((team) => adapter.deleteMany({
|
|
256
|
+
model: "teamMember",
|
|
257
|
+
where: [{
|
|
258
|
+
field: "teamId",
|
|
259
|
+
value: team.id
|
|
260
|
+
}, {
|
|
261
|
+
field: "userId",
|
|
262
|
+
value: userId
|
|
263
|
+
}]
|
|
264
|
+
})));
|
|
265
|
+
}
|
|
266
|
+
return member;
|
|
267
|
+
},
|
|
268
|
+
updateOrganization: async (organizationId, data) => {
|
|
269
|
+
const organization$1 = await (await getCurrentAdapter(baseAdapter)).update({
|
|
270
|
+
model: "organization",
|
|
271
|
+
where: [{
|
|
272
|
+
field: "id",
|
|
273
|
+
value: organizationId
|
|
274
|
+
}],
|
|
275
|
+
update: {
|
|
276
|
+
...data,
|
|
277
|
+
metadata: typeof data.metadata === "object" ? JSON.stringify(data.metadata) : data.metadata
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
if (!organization$1) return null;
|
|
281
|
+
return {
|
|
282
|
+
...organization$1,
|
|
283
|
+
metadata: organization$1.metadata ? parser_default(organization$1.metadata) : void 0
|
|
284
|
+
};
|
|
285
|
+
},
|
|
286
|
+
deleteOrganization: async (organizationId) => {
|
|
287
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
288
|
+
await adapter.delete({
|
|
289
|
+
model: "member",
|
|
290
|
+
where: [{
|
|
291
|
+
field: "organizationId",
|
|
292
|
+
value: organizationId
|
|
293
|
+
}]
|
|
294
|
+
});
|
|
295
|
+
await adapter.delete({
|
|
296
|
+
model: "invitation",
|
|
297
|
+
where: [{
|
|
298
|
+
field: "organizationId",
|
|
299
|
+
value: organizationId
|
|
300
|
+
}]
|
|
301
|
+
});
|
|
302
|
+
await adapter.delete({
|
|
303
|
+
model: "organization",
|
|
304
|
+
where: [{
|
|
305
|
+
field: "id",
|
|
306
|
+
value: organizationId
|
|
307
|
+
}]
|
|
308
|
+
});
|
|
309
|
+
return organizationId;
|
|
310
|
+
},
|
|
311
|
+
setActiveOrganization: async (sessionToken, organizationId, ctx) => {
|
|
312
|
+
return await context.internalAdapter.updateSession(sessionToken, { activeOrganizationId: organizationId });
|
|
313
|
+
},
|
|
314
|
+
findOrganizationById: async (organizationId) => {
|
|
315
|
+
return await (await getCurrentAdapter(baseAdapter)).findOne({
|
|
316
|
+
model: "organization",
|
|
317
|
+
where: [{
|
|
318
|
+
field: "id",
|
|
319
|
+
value: organizationId
|
|
320
|
+
}]
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
checkMembership: async ({ userId, organizationId }) => {
|
|
324
|
+
return await (await getCurrentAdapter(baseAdapter)).findOne({
|
|
325
|
+
model: "member",
|
|
326
|
+
where: [{
|
|
327
|
+
field: "userId",
|
|
328
|
+
value: userId
|
|
329
|
+
}, {
|
|
330
|
+
field: "organizationId",
|
|
331
|
+
value: organizationId
|
|
332
|
+
}]
|
|
333
|
+
});
|
|
334
|
+
},
|
|
335
|
+
findFullOrganization: async ({ organizationId, isSlug, includeTeams, membersLimit }) => {
|
|
336
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
337
|
+
const org = await adapter.findOne({
|
|
338
|
+
model: "organization",
|
|
339
|
+
where: [{
|
|
340
|
+
field: isSlug ? "slug" : "id",
|
|
341
|
+
value: organizationId
|
|
342
|
+
}]
|
|
343
|
+
});
|
|
344
|
+
if (!org) return null;
|
|
345
|
+
const [invitations, members, teams] = await Promise.all([
|
|
346
|
+
adapter.findMany({
|
|
347
|
+
model: "invitation",
|
|
348
|
+
where: [{
|
|
349
|
+
field: "organizationId",
|
|
350
|
+
value: org.id
|
|
351
|
+
}]
|
|
352
|
+
}),
|
|
353
|
+
adapter.findMany({
|
|
354
|
+
model: "member",
|
|
355
|
+
where: [{
|
|
356
|
+
field: "organizationId",
|
|
357
|
+
value: org.id
|
|
358
|
+
}],
|
|
359
|
+
limit: membersLimit ?? options?.membershipLimit ?? 100
|
|
360
|
+
}),
|
|
361
|
+
includeTeams ? adapter.findMany({
|
|
362
|
+
model: "team",
|
|
363
|
+
where: [{
|
|
364
|
+
field: "organizationId",
|
|
365
|
+
value: org.id
|
|
366
|
+
}]
|
|
367
|
+
}) : null
|
|
368
|
+
]);
|
|
369
|
+
if (!org) return null;
|
|
370
|
+
const userIds = members.map((member) => member.userId);
|
|
371
|
+
const users = userIds.length > 0 ? await adapter.findMany({
|
|
372
|
+
model: "user",
|
|
373
|
+
where: [{
|
|
374
|
+
field: "id",
|
|
375
|
+
value: userIds,
|
|
376
|
+
operator: "in"
|
|
377
|
+
}],
|
|
378
|
+
limit: options?.membershipLimit || 100
|
|
379
|
+
}) : [];
|
|
380
|
+
const userMap = new Map(users.map((user) => [user.id, user]));
|
|
381
|
+
const membersWithUsers = members.map((member) => {
|
|
382
|
+
const user = userMap.get(member.userId);
|
|
383
|
+
if (!user) throw new PayvoAuthError("Unexpected error: User not found for member");
|
|
384
|
+
return {
|
|
385
|
+
...member,
|
|
386
|
+
user: {
|
|
387
|
+
id: user.id,
|
|
388
|
+
name: user.name,
|
|
389
|
+
email: user.email,
|
|
390
|
+
image: user.image
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
return {
|
|
395
|
+
...org,
|
|
396
|
+
invitations,
|
|
397
|
+
members: membersWithUsers,
|
|
398
|
+
teams
|
|
399
|
+
};
|
|
400
|
+
},
|
|
401
|
+
listOrganizations: async (userId) => {
|
|
402
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
403
|
+
const members = await adapter.findMany({
|
|
404
|
+
model: "member",
|
|
405
|
+
where: [{
|
|
406
|
+
field: "userId",
|
|
407
|
+
value: userId
|
|
408
|
+
}]
|
|
409
|
+
});
|
|
410
|
+
if (!members || members.length === 0) return [];
|
|
411
|
+
const organizationIds = members.map((member) => member.organizationId);
|
|
412
|
+
return await adapter.findMany({
|
|
413
|
+
model: "organization",
|
|
414
|
+
where: [{
|
|
415
|
+
field: "id",
|
|
416
|
+
value: organizationIds,
|
|
417
|
+
operator: "in"
|
|
418
|
+
}]
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
createTeam: async (data) => {
|
|
422
|
+
return await (await getCurrentAdapter(baseAdapter)).create({
|
|
423
|
+
model: "team",
|
|
424
|
+
data
|
|
425
|
+
});
|
|
426
|
+
},
|
|
427
|
+
findTeamById: async ({ teamId, organizationId, includeTeamMembers }) => {
|
|
428
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
429
|
+
const team = await adapter.findOne({
|
|
430
|
+
model: "team",
|
|
431
|
+
where: [{
|
|
432
|
+
field: "id",
|
|
433
|
+
value: teamId
|
|
434
|
+
}, ...organizationId ? [{
|
|
435
|
+
field: "organizationId",
|
|
436
|
+
value: organizationId
|
|
437
|
+
}] : []]
|
|
438
|
+
});
|
|
439
|
+
if (!team) return null;
|
|
440
|
+
let members = [];
|
|
441
|
+
if (includeTeamMembers) {
|
|
442
|
+
members = await adapter.findMany({
|
|
443
|
+
model: "teamMember",
|
|
444
|
+
where: [{
|
|
445
|
+
field: "teamId",
|
|
446
|
+
value: teamId
|
|
447
|
+
}],
|
|
448
|
+
limit: options?.membershipLimit || 100
|
|
449
|
+
});
|
|
450
|
+
return {
|
|
451
|
+
...team,
|
|
452
|
+
members
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return team;
|
|
456
|
+
},
|
|
457
|
+
updateTeam: async (teamId, data) => {
|
|
458
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
459
|
+
if ("id" in data) data.id = void 0;
|
|
460
|
+
return await adapter.update({
|
|
461
|
+
model: "team",
|
|
462
|
+
where: [{
|
|
463
|
+
field: "id",
|
|
464
|
+
value: teamId
|
|
465
|
+
}],
|
|
466
|
+
update: { ...data }
|
|
467
|
+
});
|
|
468
|
+
},
|
|
469
|
+
deleteTeam: async (teamId) => {
|
|
470
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
471
|
+
await adapter.deleteMany({
|
|
472
|
+
model: "teamMember",
|
|
473
|
+
where: [{
|
|
474
|
+
field: "teamId",
|
|
475
|
+
value: teamId
|
|
476
|
+
}]
|
|
477
|
+
});
|
|
478
|
+
return await adapter.delete({
|
|
479
|
+
model: "team",
|
|
480
|
+
where: [{
|
|
481
|
+
field: "id",
|
|
482
|
+
value: teamId
|
|
483
|
+
}]
|
|
484
|
+
});
|
|
485
|
+
},
|
|
486
|
+
listTeams: async (organizationId) => {
|
|
487
|
+
return await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
488
|
+
model: "team",
|
|
489
|
+
where: [{
|
|
490
|
+
field: "organizationId",
|
|
491
|
+
value: organizationId
|
|
492
|
+
}]
|
|
493
|
+
});
|
|
494
|
+
},
|
|
495
|
+
createTeamInvitation: async ({ email, role, teamId, organizationId, inviterId, expiresIn = 1e3 * 60 * 60 * 48 }) => {
|
|
496
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
497
|
+
const expiresAt = getDate(expiresIn);
|
|
498
|
+
return await adapter.create({
|
|
499
|
+
model: "invitation",
|
|
500
|
+
data: {
|
|
501
|
+
email,
|
|
502
|
+
role,
|
|
503
|
+
organizationId,
|
|
504
|
+
teamId,
|
|
505
|
+
inviterId,
|
|
506
|
+
status: "pending",
|
|
507
|
+
expiresAt
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
},
|
|
511
|
+
setActiveTeam: async (sessionToken, teamId, ctx) => {
|
|
512
|
+
return await context.internalAdapter.updateSession(sessionToken, { activeTeamId: teamId });
|
|
513
|
+
},
|
|
514
|
+
listTeamMembers: async (data) => {
|
|
515
|
+
return await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
516
|
+
model: "teamMember",
|
|
517
|
+
where: [{
|
|
518
|
+
field: "teamId",
|
|
519
|
+
value: data.teamId
|
|
520
|
+
}]
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
countTeamMembers: async (data) => {
|
|
524
|
+
return await (await getCurrentAdapter(baseAdapter)).count({
|
|
525
|
+
model: "teamMember",
|
|
526
|
+
where: [{
|
|
527
|
+
field: "teamId",
|
|
528
|
+
value: data.teamId
|
|
529
|
+
}]
|
|
530
|
+
});
|
|
531
|
+
},
|
|
532
|
+
countMembers: async (data) => {
|
|
533
|
+
return await (await getCurrentAdapter(baseAdapter)).count({
|
|
534
|
+
model: "member",
|
|
535
|
+
where: [{
|
|
536
|
+
field: "organizationId",
|
|
537
|
+
value: data.organizationId
|
|
538
|
+
}]
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
listTeamsByUser: async (data) => {
|
|
542
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
543
|
+
const members = await adapter.findMany({
|
|
544
|
+
model: "teamMember",
|
|
545
|
+
where: [{
|
|
546
|
+
field: "userId",
|
|
547
|
+
value: data.userId
|
|
548
|
+
}]
|
|
549
|
+
});
|
|
550
|
+
return await adapter.findMany({
|
|
551
|
+
model: "team",
|
|
552
|
+
where: [{
|
|
553
|
+
field: "id",
|
|
554
|
+
operator: "in",
|
|
555
|
+
value: members.map((m) => m.teamId)
|
|
556
|
+
}]
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
findTeamMember: async (data) => {
|
|
560
|
+
return await (await getCurrentAdapter(baseAdapter)).findOne({
|
|
561
|
+
model: "teamMember",
|
|
562
|
+
where: [{
|
|
563
|
+
field: "teamId",
|
|
564
|
+
value: data.teamId
|
|
565
|
+
}, {
|
|
566
|
+
field: "userId",
|
|
567
|
+
value: data.userId
|
|
568
|
+
}]
|
|
569
|
+
});
|
|
570
|
+
},
|
|
571
|
+
findOrCreateTeamMember: async (data) => {
|
|
572
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
573
|
+
const member = await adapter.findOne({
|
|
574
|
+
model: "teamMember",
|
|
575
|
+
where: [{
|
|
576
|
+
field: "teamId",
|
|
577
|
+
value: data.teamId
|
|
578
|
+
}, {
|
|
579
|
+
field: "userId",
|
|
580
|
+
value: data.userId
|
|
581
|
+
}]
|
|
582
|
+
});
|
|
583
|
+
if (member) return member;
|
|
584
|
+
return await adapter.create({
|
|
585
|
+
model: "teamMember",
|
|
586
|
+
data: {
|
|
587
|
+
teamId: data.teamId,
|
|
588
|
+
userId: data.userId,
|
|
589
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
},
|
|
593
|
+
removeTeamMember: async (data) => {
|
|
594
|
+
await (await getCurrentAdapter(baseAdapter)).deleteMany({
|
|
595
|
+
model: "teamMember",
|
|
596
|
+
where: [{
|
|
597
|
+
field: "teamId",
|
|
598
|
+
value: data.teamId
|
|
599
|
+
}, {
|
|
600
|
+
field: "userId",
|
|
601
|
+
value: data.userId
|
|
602
|
+
}]
|
|
603
|
+
});
|
|
604
|
+
},
|
|
605
|
+
findInvitationsByTeamId: async (teamId) => {
|
|
606
|
+
return await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
607
|
+
model: "invitation",
|
|
608
|
+
where: [{
|
|
609
|
+
field: "teamId",
|
|
610
|
+
value: teamId
|
|
611
|
+
}]
|
|
612
|
+
});
|
|
613
|
+
},
|
|
614
|
+
listUserInvitations: async (email) => {
|
|
615
|
+
return await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
616
|
+
model: "invitation",
|
|
617
|
+
where: [{
|
|
618
|
+
field: "email",
|
|
619
|
+
value: email.toLowerCase()
|
|
620
|
+
}]
|
|
621
|
+
});
|
|
622
|
+
},
|
|
623
|
+
createInvitation: async ({ invitation, user }) => {
|
|
624
|
+
const adapter = await getCurrentAdapter(baseAdapter);
|
|
625
|
+
const expiresAt = getDate(options?.invitationExpiresIn || 3600 * 48, "sec");
|
|
626
|
+
return await adapter.create({
|
|
627
|
+
model: "invitation",
|
|
628
|
+
data: {
|
|
629
|
+
status: "pending",
|
|
630
|
+
expiresAt,
|
|
631
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
632
|
+
inviterId: user.id,
|
|
633
|
+
...invitation,
|
|
634
|
+
teamId: invitation.teamIds.length > 0 ? invitation.teamIds.join(",") : null
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
},
|
|
638
|
+
findInvitationById: async (id) => {
|
|
639
|
+
return await (await getCurrentAdapter(baseAdapter)).findOne({
|
|
640
|
+
model: "invitation",
|
|
641
|
+
where: [{
|
|
642
|
+
field: "id",
|
|
643
|
+
value: id
|
|
644
|
+
}]
|
|
645
|
+
});
|
|
646
|
+
},
|
|
647
|
+
findPendingInvitation: async (data) => {
|
|
648
|
+
return (await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
649
|
+
model: "invitation",
|
|
650
|
+
where: [
|
|
651
|
+
{
|
|
652
|
+
field: "email",
|
|
653
|
+
value: data.email.toLowerCase()
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
field: "organizationId",
|
|
657
|
+
value: data.organizationId
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
field: "status",
|
|
661
|
+
value: "pending"
|
|
662
|
+
}
|
|
663
|
+
]
|
|
664
|
+
})).filter((invite) => new Date(invite.expiresAt) > /* @__PURE__ */ new Date());
|
|
665
|
+
},
|
|
666
|
+
findPendingInvitations: async (data) => {
|
|
667
|
+
return (await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
668
|
+
model: "invitation",
|
|
669
|
+
where: [{
|
|
670
|
+
field: "organizationId",
|
|
671
|
+
value: data.organizationId
|
|
672
|
+
}, {
|
|
673
|
+
field: "status",
|
|
674
|
+
value: "pending"
|
|
675
|
+
}]
|
|
676
|
+
})).filter((invite) => new Date(invite.expiresAt) > /* @__PURE__ */ new Date());
|
|
677
|
+
},
|
|
678
|
+
listInvitations: async (data) => {
|
|
679
|
+
return await (await getCurrentAdapter(baseAdapter)).findMany({
|
|
680
|
+
model: "invitation",
|
|
681
|
+
where: [{
|
|
682
|
+
field: "organizationId",
|
|
683
|
+
value: data.organizationId
|
|
684
|
+
}]
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
updateInvitation: async (data) => {
|
|
688
|
+
return await (await getCurrentAdapter(baseAdapter)).update({
|
|
689
|
+
model: "invitation",
|
|
690
|
+
where: [{
|
|
691
|
+
field: "id",
|
|
692
|
+
value: data.invitationId
|
|
693
|
+
}],
|
|
694
|
+
update: { status: data.status }
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/plugins/organization/call.ts
|
|
702
|
+
const orgMiddleware = createAuthMiddleware(async () => {
|
|
703
|
+
return {};
|
|
704
|
+
});
|
|
705
|
+
/**
|
|
706
|
+
* The middleware forces the endpoint to require a valid session by utilizing the `sessionMiddleware`.
|
|
707
|
+
* It also appends additional types to the session type regarding organizations.
|
|
708
|
+
*/
|
|
709
|
+
const orgSessionMiddleware = createAuthMiddleware({ use: [sessionMiddleware] }, async (ctx) => {
|
|
710
|
+
return { session: ctx.context.session };
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/plugins/organization/error-codes.ts
|
|
715
|
+
const ORGANIZATION_ERROR_CODES = defineErrorCodes({
|
|
716
|
+
YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION: "You are not allowed to create a new organization",
|
|
717
|
+
YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS: "You have reached the maximum number of organizations",
|
|
718
|
+
ORGANIZATION_ALREADY_EXISTS: "Organization already exists",
|
|
719
|
+
ORGANIZATION_SLUG_ALREADY_TAKEN: "Organization slug already taken",
|
|
720
|
+
ORGANIZATION_NOT_FOUND: "Organization not found",
|
|
721
|
+
USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION: "User is not a member of the organization",
|
|
722
|
+
YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION: "You are not allowed to update this organization",
|
|
723
|
+
YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION: "You are not allowed to delete this organization",
|
|
724
|
+
NO_ACTIVE_ORGANIZATION: "No active organization",
|
|
725
|
+
USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION: "User is already a member of this organization",
|
|
726
|
+
MEMBER_NOT_FOUND: "Member not found",
|
|
727
|
+
ROLE_NOT_FOUND: "Role not found",
|
|
728
|
+
YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM: "You are not allowed to create a new team",
|
|
729
|
+
TEAM_ALREADY_EXISTS: "Team already exists",
|
|
730
|
+
TEAM_NOT_FOUND: "Team not found",
|
|
731
|
+
YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER: "You cannot leave the organization as the only owner",
|
|
732
|
+
YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER: "You cannot leave the organization without an owner",
|
|
733
|
+
YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER: "You are not allowed to delete this member",
|
|
734
|
+
YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION: "You are not allowed to invite users to this organization",
|
|
735
|
+
USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION: "User is already invited to this organization",
|
|
736
|
+
INVITATION_NOT_FOUND: "Invitation not found",
|
|
737
|
+
YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION: "You are not the recipient of the invitation",
|
|
738
|
+
EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION: "Email verification required before accepting or rejecting invitation",
|
|
739
|
+
YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION: "You are not allowed to cancel this invitation",
|
|
740
|
+
INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION: "Inviter is no longer a member of the organization",
|
|
741
|
+
YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE: "You are not allowed to invite a user with this role",
|
|
742
|
+
FAILED_TO_RETRIEVE_INVITATION: "Failed to retrieve invitation",
|
|
743
|
+
YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS: "You have reached the maximum number of teams",
|
|
744
|
+
UNABLE_TO_REMOVE_LAST_TEAM: "Unable to remove last team",
|
|
745
|
+
YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER: "You are not allowed to update this member",
|
|
746
|
+
ORGANIZATION_MEMBERSHIP_LIMIT_REACHED: "Organization membership limit reached",
|
|
747
|
+
YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION: "You are not allowed to create teams in this organization",
|
|
748
|
+
YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION: "You are not allowed to delete teams in this organization",
|
|
749
|
+
YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM: "You are not allowed to update this team",
|
|
750
|
+
YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM: "You are not allowed to delete this team",
|
|
751
|
+
INVITATION_LIMIT_REACHED: "Invitation limit reached",
|
|
752
|
+
TEAM_MEMBER_LIMIT_REACHED: "Team member limit reached",
|
|
753
|
+
USER_IS_NOT_A_MEMBER_OF_THE_TEAM: "User is not a member of the team",
|
|
754
|
+
YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM: "You are not allowed to list the members of this team",
|
|
755
|
+
YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM: "You do not have an active team",
|
|
756
|
+
YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER: "You are not allowed to create a new member",
|
|
757
|
+
YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER: "You are not allowed to remove a team member",
|
|
758
|
+
YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION: "You are not allowed to access this organization as an owner",
|
|
759
|
+
YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION: "You are not a member of this organization",
|
|
760
|
+
MISSING_AC_INSTANCE: "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information",
|
|
761
|
+
YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE: "You must be in an organization to create a role",
|
|
762
|
+
YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE: "You are not allowed to create a role",
|
|
763
|
+
YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE: "You are not allowed to update a role",
|
|
764
|
+
YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE: "You are not allowed to delete a role",
|
|
765
|
+
YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE: "You are not allowed to read a role",
|
|
766
|
+
YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE: "You are not allowed to list a role",
|
|
767
|
+
YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE: "You are not allowed to get a role",
|
|
768
|
+
TOO_MANY_ROLES: "This organization has too many roles",
|
|
769
|
+
INVALID_RESOURCE: "The provided permission includes an invalid resource",
|
|
770
|
+
ROLE_NAME_IS_ALREADY_TAKEN: "That role name is already taken",
|
|
771
|
+
CANNOT_DELETE_A_PRE_DEFINED_ROLE: "Cannot delete a pre-defined role"
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/plugins/organization/has-permission.ts
|
|
776
|
+
const hasPermission = async (input, ctx) => {
|
|
777
|
+
let acRoles = { ...input.options.roles || defaultRoles };
|
|
778
|
+
if (ctx && input.organizationId && input.options.dynamicAccessControl?.enabled && input.options.ac && !input.useMemoryCache) {
|
|
779
|
+
const roles = await ctx.context.adapter.findMany({
|
|
780
|
+
model: "organizationRole",
|
|
781
|
+
where: [{
|
|
782
|
+
field: "organizationId",
|
|
783
|
+
value: input.organizationId
|
|
784
|
+
}]
|
|
785
|
+
});
|
|
786
|
+
for (const { role, permission: permissionsString } of roles) {
|
|
787
|
+
if (role in acRoles) continue;
|
|
788
|
+
const result = z$1.record(z$1.string(), z$1.array(z$1.string())).safeParse(JSON.parse(permissionsString));
|
|
789
|
+
if (!result.success) {
|
|
790
|
+
ctx.context.logger.error("[hasPermission] Invalid permissions for role " + role, { permissions: JSON.parse(permissionsString) });
|
|
791
|
+
throw new APIError$1("INTERNAL_SERVER_ERROR", { message: "Invalid permissions for role " + role });
|
|
792
|
+
}
|
|
793
|
+
acRoles[role] = input.options.ac.newRole(result.data);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (input.useMemoryCache) acRoles = cacheAllRoles.get(input.organizationId) || acRoles;
|
|
797
|
+
cacheAllRoles.set(input.organizationId, acRoles);
|
|
798
|
+
return hasPermissionFn(input, acRoles);
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
//#endregion
|
|
802
|
+
//#region src/plugins/organization/routes/crud-access-control.ts
|
|
803
|
+
const normalizeRoleName = (role) => role.toLowerCase();
|
|
804
|
+
const DEFAULT_MAXIMUM_ROLES_PER_ORGANIZATION = Number.POSITIVE_INFINITY;
|
|
805
|
+
const getAdditionalFields = (options, shouldBePartial = false) => {
|
|
806
|
+
let additionalFields = options?.schema?.organizationRole?.additionalFields || {};
|
|
807
|
+
if (shouldBePartial) for (const key in additionalFields) additionalFields[key].required = false;
|
|
808
|
+
return {
|
|
809
|
+
additionalFieldsSchema: toZodSchema({
|
|
810
|
+
fields: additionalFields,
|
|
811
|
+
isClientSide: true
|
|
812
|
+
}),
|
|
813
|
+
$AdditionalFields: {},
|
|
814
|
+
$ReturnAdditionalFields: {}
|
|
815
|
+
};
|
|
816
|
+
};
|
|
817
|
+
const createOrgRole = (options) => {
|
|
818
|
+
const { additionalFieldsSchema, $AdditionalFields, $ReturnAdditionalFields } = getAdditionalFields(options, false);
|
|
819
|
+
return createAuthEndpoint("/organization/create-role", {
|
|
820
|
+
method: "POST",
|
|
821
|
+
body: z$1.object({
|
|
822
|
+
organizationId: z$1.string().optional().meta({ description: "The id of the organization to create the role in. If not provided, the user's active organization will be used." }),
|
|
823
|
+
role: z$1.string().meta({ description: "The name of the role to create" }),
|
|
824
|
+
permission: z$1.record(z$1.string(), z$1.array(z$1.string())).meta({ description: "The permission to assign to the role" }),
|
|
825
|
+
additionalFields: z$1.object({ ...additionalFieldsSchema.shape }).optional()
|
|
826
|
+
}),
|
|
827
|
+
metadata: { $Infer: { body: {} } },
|
|
828
|
+
requireHeaders: true,
|
|
829
|
+
use: [orgSessionMiddleware]
|
|
830
|
+
}, async (ctx) => {
|
|
831
|
+
const { session, user } = ctx.context.session;
|
|
832
|
+
let roleName = ctx.body.role;
|
|
833
|
+
const permission = ctx.body.permission;
|
|
834
|
+
const additionalFields = ctx.body.additionalFields;
|
|
835
|
+
const ac = options.ac;
|
|
836
|
+
if (!ac) {
|
|
837
|
+
ctx.context.logger.error(`[Dynamic Access Control] The organization plugin is missing a pre-defined ac instance.`, `\nPlease refer to the documentation here: https://payvopayvo.com/docs/plugins/organization#dynamic-access-control`);
|
|
838
|
+
throw new APIError$1("NOT_IMPLEMENTED", { message: ORGANIZATION_ERROR_CODES.MISSING_AC_INSTANCE });
|
|
839
|
+
}
|
|
840
|
+
const organizationId = ctx.body.organizationId ?? session.activeOrganizationId;
|
|
841
|
+
if (!organizationId) {
|
|
842
|
+
ctx.context.logger.error(`[Dynamic Access Control] The session is missing an active organization id to create a role. Either set an active org id, or pass an organizationId in the request body.`);
|
|
843
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE });
|
|
844
|
+
}
|
|
845
|
+
roleName = normalizeRoleName(roleName);
|
|
846
|
+
await checkIfRoleNameIsTakenByPreDefinedRole({
|
|
847
|
+
role: roleName,
|
|
848
|
+
organizationId,
|
|
849
|
+
options,
|
|
850
|
+
ctx
|
|
851
|
+
});
|
|
852
|
+
const member = await ctx.context.adapter.findOne({
|
|
853
|
+
model: "member",
|
|
854
|
+
where: [{
|
|
855
|
+
field: "organizationId",
|
|
856
|
+
value: organizationId,
|
|
857
|
+
operator: "eq",
|
|
858
|
+
connector: "AND"
|
|
859
|
+
}, {
|
|
860
|
+
field: "userId",
|
|
861
|
+
value: user.id,
|
|
862
|
+
operator: "eq",
|
|
863
|
+
connector: "AND"
|
|
864
|
+
}]
|
|
865
|
+
});
|
|
866
|
+
if (!member) {
|
|
867
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not a member of the organization to create a role.`, {
|
|
868
|
+
userId: user.id,
|
|
869
|
+
organizationId
|
|
870
|
+
});
|
|
871
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
872
|
+
}
|
|
873
|
+
if (!await hasPermission({
|
|
874
|
+
options,
|
|
875
|
+
organizationId,
|
|
876
|
+
permissions: { ac: ["create"] },
|
|
877
|
+
role: member.role
|
|
878
|
+
}, ctx)) {
|
|
879
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not permitted to create a role. If this is unexpected, please make sure the role associated to that member has the "ac" resource with the "create" permission.`, {
|
|
880
|
+
userId: user.id,
|
|
881
|
+
organizationId,
|
|
882
|
+
role: member.role
|
|
883
|
+
});
|
|
884
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE });
|
|
885
|
+
}
|
|
886
|
+
const maximumRolesPerOrganization = typeof options.dynamicAccessControl?.maximumRolesPerOrganization === "function" ? await options.dynamicAccessControl.maximumRolesPerOrganization(organizationId) : options.dynamicAccessControl?.maximumRolesPerOrganization ?? DEFAULT_MAXIMUM_ROLES_PER_ORGANIZATION;
|
|
887
|
+
const rolesInDB = await ctx.context.adapter.count({
|
|
888
|
+
model: "organizationRole",
|
|
889
|
+
where: [{
|
|
890
|
+
field: "organizationId",
|
|
891
|
+
value: organizationId,
|
|
892
|
+
operator: "eq",
|
|
893
|
+
connector: "AND"
|
|
894
|
+
}]
|
|
895
|
+
});
|
|
896
|
+
if (rolesInDB >= maximumRolesPerOrganization) {
|
|
897
|
+
ctx.context.logger.error(`[Dynamic Access Control] Failed to create a new role, the organization has too many roles. Maximum allowed roles is ${maximumRolesPerOrganization}.`, {
|
|
898
|
+
organizationId,
|
|
899
|
+
maximumRolesPerOrganization,
|
|
900
|
+
rolesInDB
|
|
901
|
+
});
|
|
902
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TOO_MANY_ROLES });
|
|
903
|
+
}
|
|
904
|
+
await checkForInvalidResources({
|
|
905
|
+
ac,
|
|
906
|
+
ctx,
|
|
907
|
+
permission
|
|
908
|
+
});
|
|
909
|
+
await checkIfMemberHasPermission({
|
|
910
|
+
ctx,
|
|
911
|
+
member,
|
|
912
|
+
options,
|
|
913
|
+
organizationId,
|
|
914
|
+
permissionRequired: permission,
|
|
915
|
+
user,
|
|
916
|
+
action: "create"
|
|
917
|
+
});
|
|
918
|
+
await checkIfRoleNameIsTakenByRoleInDB({
|
|
919
|
+
ctx,
|
|
920
|
+
organizationId,
|
|
921
|
+
role: roleName
|
|
922
|
+
});
|
|
923
|
+
const newRole = ac.newRole(permission);
|
|
924
|
+
const data = {
|
|
925
|
+
...await ctx.context.adapter.create({
|
|
926
|
+
model: "organizationRole",
|
|
927
|
+
data: {
|
|
928
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
929
|
+
organizationId,
|
|
930
|
+
permission: JSON.stringify(permission),
|
|
931
|
+
role: roleName,
|
|
932
|
+
...additionalFields
|
|
933
|
+
}
|
|
934
|
+
}),
|
|
935
|
+
permission
|
|
936
|
+
};
|
|
937
|
+
return ctx.json({
|
|
938
|
+
success: true,
|
|
939
|
+
roleData: data,
|
|
940
|
+
statements: newRole.statements
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
};
|
|
944
|
+
const deleteOrgRole = (options) => {
|
|
945
|
+
return createAuthEndpoint("/organization/delete-role", {
|
|
946
|
+
method: "POST",
|
|
947
|
+
body: z$1.object({ organizationId: z$1.string().optional().meta({ description: "The id of the organization to create the role in. If not provided, the user's active organization will be used." }) }).and(z$1.union([z$1.object({ roleName: z$1.string().nonempty().meta({ description: "The name of the role to delete" }) }), z$1.object({ roleId: z$1.string().nonempty().meta({ description: "The id of the role to delete" }) })])),
|
|
948
|
+
requireHeaders: true,
|
|
949
|
+
use: [orgSessionMiddleware],
|
|
950
|
+
metadata: { $Infer: { body: {} } }
|
|
951
|
+
}, async (ctx) => {
|
|
952
|
+
const { session, user } = ctx.context.session;
|
|
953
|
+
const organizationId = ctx.body.organizationId ?? session.activeOrganizationId;
|
|
954
|
+
if (!organizationId) {
|
|
955
|
+
ctx.context.logger.error(`[Dynamic Access Control] The session is missing an active organization id to delete a role. Either set an active org id, or pass an organizationId in the request body.`);
|
|
956
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
957
|
+
}
|
|
958
|
+
const member = await ctx.context.adapter.findOne({
|
|
959
|
+
model: "member",
|
|
960
|
+
where: [{
|
|
961
|
+
field: "organizationId",
|
|
962
|
+
value: organizationId,
|
|
963
|
+
operator: "eq",
|
|
964
|
+
connector: "AND"
|
|
965
|
+
}, {
|
|
966
|
+
field: "userId",
|
|
967
|
+
value: user.id,
|
|
968
|
+
operator: "eq",
|
|
969
|
+
connector: "AND"
|
|
970
|
+
}]
|
|
971
|
+
});
|
|
972
|
+
if (!member) {
|
|
973
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not a member of the organization to delete a role.`, {
|
|
974
|
+
userId: user.id,
|
|
975
|
+
organizationId
|
|
976
|
+
});
|
|
977
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
978
|
+
}
|
|
979
|
+
if (!await hasPermission({
|
|
980
|
+
options,
|
|
981
|
+
organizationId,
|
|
982
|
+
permissions: { ac: ["delete"] },
|
|
983
|
+
role: member.role
|
|
984
|
+
}, ctx)) {
|
|
985
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not permitted to delete a role. If this is unexpected, please make sure the role associated to that member has the "ac" resource with the "delete" permission.`, {
|
|
986
|
+
userId: user.id,
|
|
987
|
+
organizationId,
|
|
988
|
+
role: member.role
|
|
989
|
+
});
|
|
990
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE });
|
|
991
|
+
}
|
|
992
|
+
if (ctx.body.roleName) {
|
|
993
|
+
const roleName = ctx.body.roleName;
|
|
994
|
+
const defaultRoles$2 = options.roles ? Object.keys(options.roles) : [
|
|
995
|
+
"owner",
|
|
996
|
+
"admin",
|
|
997
|
+
"member"
|
|
998
|
+
];
|
|
999
|
+
if (defaultRoles$2.includes(roleName)) {
|
|
1000
|
+
ctx.context.logger.error(`[Dynamic Access Control] Cannot delete a pre-defined role.`, {
|
|
1001
|
+
roleName,
|
|
1002
|
+
organizationId,
|
|
1003
|
+
defaultRoles: defaultRoles$2
|
|
1004
|
+
});
|
|
1005
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.CANNOT_DELETE_A_PRE_DEFINED_ROLE });
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
let condition;
|
|
1009
|
+
if (ctx.body.roleName) condition = {
|
|
1010
|
+
field: "role",
|
|
1011
|
+
value: ctx.body.roleName,
|
|
1012
|
+
operator: "eq",
|
|
1013
|
+
connector: "AND"
|
|
1014
|
+
};
|
|
1015
|
+
else if (ctx.body.roleId) condition = {
|
|
1016
|
+
field: "id",
|
|
1017
|
+
value: ctx.body.roleId,
|
|
1018
|
+
operator: "eq",
|
|
1019
|
+
connector: "AND"
|
|
1020
|
+
};
|
|
1021
|
+
else {
|
|
1022
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name/id is not provided in the request body.`);
|
|
1023
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND });
|
|
1024
|
+
}
|
|
1025
|
+
const existingRoleInDB = await ctx.context.adapter.findOne({
|
|
1026
|
+
model: "organizationRole",
|
|
1027
|
+
where: [{
|
|
1028
|
+
field: "organizationId",
|
|
1029
|
+
value: organizationId,
|
|
1030
|
+
operator: "eq",
|
|
1031
|
+
connector: "AND"
|
|
1032
|
+
}, condition]
|
|
1033
|
+
});
|
|
1034
|
+
if (!existingRoleInDB) {
|
|
1035
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name/id does not exist in the database.`, {
|
|
1036
|
+
..."roleName" in ctx.body ? { roleName: ctx.body.roleName } : { roleId: ctx.body.roleId },
|
|
1037
|
+
organizationId
|
|
1038
|
+
});
|
|
1039
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND });
|
|
1040
|
+
}
|
|
1041
|
+
existingRoleInDB.permission = JSON.parse(existingRoleInDB.permission);
|
|
1042
|
+
await ctx.context.adapter.delete({
|
|
1043
|
+
model: "organizationRole",
|
|
1044
|
+
where: [{
|
|
1045
|
+
field: "organizationId",
|
|
1046
|
+
value: organizationId,
|
|
1047
|
+
operator: "eq",
|
|
1048
|
+
connector: "AND"
|
|
1049
|
+
}, condition]
|
|
1050
|
+
});
|
|
1051
|
+
return ctx.json({ success: true });
|
|
1052
|
+
});
|
|
1053
|
+
};
|
|
1054
|
+
const listOrgRoles = (options) => {
|
|
1055
|
+
const { $ReturnAdditionalFields } = getAdditionalFields(options, false);
|
|
1056
|
+
return createAuthEndpoint("/organization/list-roles", {
|
|
1057
|
+
method: "GET",
|
|
1058
|
+
use: [orgSessionMiddleware],
|
|
1059
|
+
query: z$1.object({ organizationId: z$1.string().optional().meta({ description: "The id of the organization to list roles for. If not provided, the user's active organization will be used." }) }).optional()
|
|
1060
|
+
}, async (ctx) => {
|
|
1061
|
+
const { session, user } = ctx.context.session;
|
|
1062
|
+
const organizationId = ctx.query?.organizationId ?? session.activeOrganizationId;
|
|
1063
|
+
if (!organizationId) {
|
|
1064
|
+
ctx.context.logger.error(`[Dynamic Access Control] The session is missing an active organization id to list roles. Either set an active org id, or pass an organizationId in the request query.`);
|
|
1065
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
1066
|
+
}
|
|
1067
|
+
const member = await ctx.context.adapter.findOne({
|
|
1068
|
+
model: "member",
|
|
1069
|
+
where: [{
|
|
1070
|
+
field: "organizationId",
|
|
1071
|
+
value: organizationId,
|
|
1072
|
+
operator: "eq",
|
|
1073
|
+
connector: "AND"
|
|
1074
|
+
}, {
|
|
1075
|
+
field: "userId",
|
|
1076
|
+
value: user.id,
|
|
1077
|
+
operator: "eq",
|
|
1078
|
+
connector: "AND"
|
|
1079
|
+
}]
|
|
1080
|
+
});
|
|
1081
|
+
if (!member) {
|
|
1082
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not a member of the organization to list roles.`, {
|
|
1083
|
+
userId: user.id,
|
|
1084
|
+
organizationId
|
|
1085
|
+
});
|
|
1086
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
1087
|
+
}
|
|
1088
|
+
if (!await hasPermission({
|
|
1089
|
+
options,
|
|
1090
|
+
organizationId,
|
|
1091
|
+
permissions: { ac: ["read"] },
|
|
1092
|
+
role: member.role
|
|
1093
|
+
}, ctx)) {
|
|
1094
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not permitted to list roles.`, {
|
|
1095
|
+
userId: user.id,
|
|
1096
|
+
organizationId,
|
|
1097
|
+
role: member.role
|
|
1098
|
+
});
|
|
1099
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE });
|
|
1100
|
+
}
|
|
1101
|
+
let roles = await ctx.context.adapter.findMany({
|
|
1102
|
+
model: "organizationRole",
|
|
1103
|
+
where: [{
|
|
1104
|
+
field: "organizationId",
|
|
1105
|
+
value: organizationId,
|
|
1106
|
+
operator: "eq",
|
|
1107
|
+
connector: "AND"
|
|
1108
|
+
}]
|
|
1109
|
+
});
|
|
1110
|
+
roles = roles.map((x) => ({
|
|
1111
|
+
...x,
|
|
1112
|
+
permission: JSON.parse(x.permission)
|
|
1113
|
+
}));
|
|
1114
|
+
return ctx.json(roles);
|
|
1115
|
+
});
|
|
1116
|
+
};
|
|
1117
|
+
const getOrgRole = (options) => {
|
|
1118
|
+
const { $ReturnAdditionalFields } = getAdditionalFields(options, false);
|
|
1119
|
+
return createAuthEndpoint("/organization/get-role", {
|
|
1120
|
+
method: "GET",
|
|
1121
|
+
use: [orgSessionMiddleware],
|
|
1122
|
+
query: z$1.object({ organizationId: z$1.string().optional().meta({ description: "The id of the organization to read a role for. If not provided, the user's active organization will be used." }) }).and(z$1.union([z$1.object({ roleName: z$1.string().nonempty().meta({ description: "The name of the role to read" }) }), z$1.object({ roleId: z$1.string().nonempty().meta({ description: "The id of the role to read" }) })])).optional(),
|
|
1123
|
+
metadata: { $Infer: { query: {} } }
|
|
1124
|
+
}, async (ctx) => {
|
|
1125
|
+
const { session, user } = ctx.context.session;
|
|
1126
|
+
const organizationId = ctx.query?.organizationId ?? session.activeOrganizationId;
|
|
1127
|
+
if (!organizationId) {
|
|
1128
|
+
ctx.context.logger.error(`[Dynamic Access Control] The session is missing an active organization id to read a role. Either set an active org id, or pass an organizationId in the request query.`);
|
|
1129
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
1130
|
+
}
|
|
1131
|
+
const member = await ctx.context.adapter.findOne({
|
|
1132
|
+
model: "member",
|
|
1133
|
+
where: [{
|
|
1134
|
+
field: "organizationId",
|
|
1135
|
+
value: organizationId,
|
|
1136
|
+
operator: "eq",
|
|
1137
|
+
connector: "AND"
|
|
1138
|
+
}, {
|
|
1139
|
+
field: "userId",
|
|
1140
|
+
value: user.id,
|
|
1141
|
+
operator: "eq",
|
|
1142
|
+
connector: "AND"
|
|
1143
|
+
}]
|
|
1144
|
+
});
|
|
1145
|
+
if (!member) {
|
|
1146
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not a member of the organization to read a role.`, {
|
|
1147
|
+
userId: user.id,
|
|
1148
|
+
organizationId
|
|
1149
|
+
});
|
|
1150
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
1151
|
+
}
|
|
1152
|
+
if (!await hasPermission({
|
|
1153
|
+
options,
|
|
1154
|
+
organizationId,
|
|
1155
|
+
permissions: { ac: ["read"] },
|
|
1156
|
+
role: member.role
|
|
1157
|
+
}, ctx)) {
|
|
1158
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not permitted to read a role.`, {
|
|
1159
|
+
userId: user.id,
|
|
1160
|
+
organizationId,
|
|
1161
|
+
role: member.role
|
|
1162
|
+
});
|
|
1163
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE });
|
|
1164
|
+
}
|
|
1165
|
+
let condition;
|
|
1166
|
+
if (ctx.query.roleName) condition = {
|
|
1167
|
+
field: "role",
|
|
1168
|
+
value: ctx.query.roleName,
|
|
1169
|
+
operator: "eq",
|
|
1170
|
+
connector: "AND"
|
|
1171
|
+
};
|
|
1172
|
+
else if (ctx.query.roleId) condition = {
|
|
1173
|
+
field: "id",
|
|
1174
|
+
value: ctx.query.roleId,
|
|
1175
|
+
operator: "eq",
|
|
1176
|
+
connector: "AND"
|
|
1177
|
+
};
|
|
1178
|
+
else {
|
|
1179
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name/id is not provided in the request query.`);
|
|
1180
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND });
|
|
1181
|
+
}
|
|
1182
|
+
let role = await ctx.context.adapter.findOne({
|
|
1183
|
+
model: "organizationRole",
|
|
1184
|
+
where: [{
|
|
1185
|
+
field: "organizationId",
|
|
1186
|
+
value: organizationId,
|
|
1187
|
+
operator: "eq",
|
|
1188
|
+
connector: "AND"
|
|
1189
|
+
}, condition]
|
|
1190
|
+
});
|
|
1191
|
+
if (!role) {
|
|
1192
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name/id does not exist in the database.`, {
|
|
1193
|
+
..."roleName" in ctx.query ? { roleName: ctx.query.roleName } : { roleId: ctx.query.roleId },
|
|
1194
|
+
organizationId
|
|
1195
|
+
});
|
|
1196
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND });
|
|
1197
|
+
}
|
|
1198
|
+
role.permission = JSON.parse(role.permission);
|
|
1199
|
+
return ctx.json(role);
|
|
1200
|
+
});
|
|
1201
|
+
};
|
|
1202
|
+
const updateOrgRole = (options) => {
|
|
1203
|
+
const { additionalFieldsSchema, $AdditionalFields, $ReturnAdditionalFields } = getAdditionalFields(options, true);
|
|
1204
|
+
return createAuthEndpoint("/organization/update-role", {
|
|
1205
|
+
method: "POST",
|
|
1206
|
+
body: z$1.object({
|
|
1207
|
+
organizationId: z$1.string().optional().meta({ description: "The id of the organization to update the role in. If not provided, the user's active organization will be used." }),
|
|
1208
|
+
data: z$1.object({
|
|
1209
|
+
permission: z$1.record(z$1.string(), z$1.array(z$1.string())).optional().meta({ description: "The permission to update the role with" }),
|
|
1210
|
+
roleName: z$1.string().optional().meta({ description: "The name of the role to update" }),
|
|
1211
|
+
...additionalFieldsSchema.shape
|
|
1212
|
+
})
|
|
1213
|
+
}).and(z$1.union([z$1.object({ roleName: z$1.string().nonempty().meta({ description: "The name of the role to update" }) }), z$1.object({ roleId: z$1.string().nonempty().meta({ description: "The id of the role to update" }) })])),
|
|
1214
|
+
metadata: { $Infer: { body: {} } },
|
|
1215
|
+
use: [orgSessionMiddleware]
|
|
1216
|
+
}, async (ctx) => {
|
|
1217
|
+
const { session, user } = ctx.context.session;
|
|
1218
|
+
const ac = options.ac;
|
|
1219
|
+
if (!ac) {
|
|
1220
|
+
ctx.context.logger.error(`[Dynamic Access Control] The organization plugin is missing a pre-defined ac instance.`, `\nPlease refer to the documentation here: https://payvopayvo.com/docs/plugins/organization#dynamic-access-control`);
|
|
1221
|
+
throw new APIError$1("NOT_IMPLEMENTED", { message: ORGANIZATION_ERROR_CODES.MISSING_AC_INSTANCE });
|
|
1222
|
+
}
|
|
1223
|
+
const organizationId = ctx.body.organizationId ?? session.activeOrganizationId;
|
|
1224
|
+
if (!organizationId) {
|
|
1225
|
+
ctx.context.logger.error(`[Dynamic Access Control] The session is missing an active organization id to update a role. Either set an active org id, or pass an organizationId in the request body.`);
|
|
1226
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
1227
|
+
}
|
|
1228
|
+
const member = await ctx.context.adapter.findOne({
|
|
1229
|
+
model: "member",
|
|
1230
|
+
where: [{
|
|
1231
|
+
field: "organizationId",
|
|
1232
|
+
value: organizationId,
|
|
1233
|
+
operator: "eq",
|
|
1234
|
+
connector: "AND"
|
|
1235
|
+
}, {
|
|
1236
|
+
field: "userId",
|
|
1237
|
+
value: user.id,
|
|
1238
|
+
operator: "eq",
|
|
1239
|
+
connector: "AND"
|
|
1240
|
+
}]
|
|
1241
|
+
});
|
|
1242
|
+
if (!member) {
|
|
1243
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not a member of the organization to update a role.`, {
|
|
1244
|
+
userId: user.id,
|
|
1245
|
+
organizationId
|
|
1246
|
+
});
|
|
1247
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
1248
|
+
}
|
|
1249
|
+
if (!await hasPermission({
|
|
1250
|
+
options,
|
|
1251
|
+
organizationId,
|
|
1252
|
+
role: member.role,
|
|
1253
|
+
permissions: { ac: ["update"] }
|
|
1254
|
+
}, ctx)) {
|
|
1255
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is not permitted to update a role.`);
|
|
1256
|
+
throw new APIError$1("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE });
|
|
1257
|
+
}
|
|
1258
|
+
let condition;
|
|
1259
|
+
if (ctx.body.roleName) condition = {
|
|
1260
|
+
field: "role",
|
|
1261
|
+
value: ctx.body.roleName,
|
|
1262
|
+
operator: "eq",
|
|
1263
|
+
connector: "AND"
|
|
1264
|
+
};
|
|
1265
|
+
else if (ctx.body.roleId) condition = {
|
|
1266
|
+
field: "id",
|
|
1267
|
+
value: ctx.body.roleId,
|
|
1268
|
+
operator: "eq",
|
|
1269
|
+
connector: "AND"
|
|
1270
|
+
};
|
|
1271
|
+
else {
|
|
1272
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name/id is not provided in the request body.`);
|
|
1273
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND });
|
|
1274
|
+
}
|
|
1275
|
+
let role = await ctx.context.adapter.findOne({
|
|
1276
|
+
model: "organizationRole",
|
|
1277
|
+
where: [{
|
|
1278
|
+
field: "organizationId",
|
|
1279
|
+
value: organizationId,
|
|
1280
|
+
operator: "eq",
|
|
1281
|
+
connector: "AND"
|
|
1282
|
+
}, condition]
|
|
1283
|
+
});
|
|
1284
|
+
if (!role) {
|
|
1285
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name/id does not exist in the database.`, {
|
|
1286
|
+
..."roleName" in ctx.body ? { roleName: ctx.body.roleName } : { roleId: ctx.body.roleId },
|
|
1287
|
+
organizationId
|
|
1288
|
+
});
|
|
1289
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NOT_FOUND });
|
|
1290
|
+
}
|
|
1291
|
+
role.permission = role.permission ? JSON.parse(role.permission) : void 0;
|
|
1292
|
+
const { permission: _, roleName: __,...additionalFields } = ctx.body.data;
|
|
1293
|
+
let updateData = { ...additionalFields };
|
|
1294
|
+
if (ctx.body.data.permission) {
|
|
1295
|
+
let newPermission = ctx.body.data.permission;
|
|
1296
|
+
await checkForInvalidResources({
|
|
1297
|
+
ac,
|
|
1298
|
+
ctx,
|
|
1299
|
+
permission: newPermission
|
|
1300
|
+
});
|
|
1301
|
+
await checkIfMemberHasPermission({
|
|
1302
|
+
ctx,
|
|
1303
|
+
member,
|
|
1304
|
+
options,
|
|
1305
|
+
organizationId,
|
|
1306
|
+
permissionRequired: newPermission,
|
|
1307
|
+
user,
|
|
1308
|
+
action: "update"
|
|
1309
|
+
});
|
|
1310
|
+
updateData.permission = newPermission;
|
|
1311
|
+
}
|
|
1312
|
+
if (ctx.body.data.roleName) {
|
|
1313
|
+
let newRoleName = ctx.body.data.roleName;
|
|
1314
|
+
newRoleName = normalizeRoleName(newRoleName);
|
|
1315
|
+
await checkIfRoleNameIsTakenByPreDefinedRole({
|
|
1316
|
+
role: newRoleName,
|
|
1317
|
+
organizationId,
|
|
1318
|
+
options,
|
|
1319
|
+
ctx
|
|
1320
|
+
});
|
|
1321
|
+
await checkIfRoleNameIsTakenByRoleInDB({
|
|
1322
|
+
role: newRoleName,
|
|
1323
|
+
organizationId,
|
|
1324
|
+
ctx
|
|
1325
|
+
});
|
|
1326
|
+
updateData.role = newRoleName;
|
|
1327
|
+
}
|
|
1328
|
+
const update = {
|
|
1329
|
+
...updateData,
|
|
1330
|
+
...updateData.permission ? { permission: JSON.stringify(updateData.permission) } : {}
|
|
1331
|
+
};
|
|
1332
|
+
await ctx.context.adapter.update({
|
|
1333
|
+
model: "organizationRole",
|
|
1334
|
+
where: [{
|
|
1335
|
+
field: "organizationId",
|
|
1336
|
+
value: organizationId,
|
|
1337
|
+
operator: "eq",
|
|
1338
|
+
connector: "AND"
|
|
1339
|
+
}, condition],
|
|
1340
|
+
update
|
|
1341
|
+
});
|
|
1342
|
+
return ctx.json({
|
|
1343
|
+
success: true,
|
|
1344
|
+
roleData: {
|
|
1345
|
+
...role,
|
|
1346
|
+
...update,
|
|
1347
|
+
permission: updateData.permission || role.permission || null
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
});
|
|
1351
|
+
};
|
|
1352
|
+
async function checkForInvalidResources({ ac, ctx, permission }) {
|
|
1353
|
+
const validResources = Object.keys(ac.statements);
|
|
1354
|
+
const providedResources = Object.keys(permission);
|
|
1355
|
+
if (providedResources.some((r) => !validResources.includes(r))) {
|
|
1356
|
+
ctx.context.logger.error(`[Dynamic Access Control] The provided permission includes an invalid resource.`, {
|
|
1357
|
+
providedResources,
|
|
1358
|
+
validResources
|
|
1359
|
+
});
|
|
1360
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.INVALID_RESOURCE });
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async function checkIfMemberHasPermission({ ctx, permissionRequired: permission, options, organizationId, member, user, action }) {
|
|
1364
|
+
const hasNecessaryPermissions = [];
|
|
1365
|
+
const permissionEntries = Object.entries(permission);
|
|
1366
|
+
for await (const [resource, permissions] of permissionEntries) for await (const perm of permissions) hasNecessaryPermissions.push({
|
|
1367
|
+
resource: { [resource]: [perm] },
|
|
1368
|
+
hasPermission: await hasPermission({
|
|
1369
|
+
options,
|
|
1370
|
+
organizationId,
|
|
1371
|
+
permissions: { [resource]: [perm] },
|
|
1372
|
+
useMemoryCache: true,
|
|
1373
|
+
role: member.role
|
|
1374
|
+
}, ctx)
|
|
1375
|
+
});
|
|
1376
|
+
const missingPermissions = hasNecessaryPermissions.filter((x) => x.hasPermission === false).map((x) => {
|
|
1377
|
+
const key = Object.keys(x.resource)[0];
|
|
1378
|
+
return `${key}:${x.resource[key][0]}`;
|
|
1379
|
+
});
|
|
1380
|
+
if (missingPermissions.length > 0) {
|
|
1381
|
+
ctx.context.logger.error(`[Dynamic Access Control] The user is missing permissions nessesary to ${action} a role with those set of permissions.\n`, {
|
|
1382
|
+
userId: user.id,
|
|
1383
|
+
organizationId,
|
|
1384
|
+
role: member.role,
|
|
1385
|
+
missingPermissions
|
|
1386
|
+
});
|
|
1387
|
+
let errorMessage;
|
|
1388
|
+
if (action === "create") errorMessage = ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE;
|
|
1389
|
+
else if (action === "update") errorMessage = ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE;
|
|
1390
|
+
else if (action === "delete") errorMessage = ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE;
|
|
1391
|
+
else if (action === "read") errorMessage = ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE;
|
|
1392
|
+
else if (action === "list") errorMessage = ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE;
|
|
1393
|
+
else errorMessage = ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE;
|
|
1394
|
+
throw new APIError$1("FORBIDDEN", {
|
|
1395
|
+
message: errorMessage,
|
|
1396
|
+
missingPermissions
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
async function checkIfRoleNameIsTakenByPreDefinedRole({ options, organizationId, role, ctx }) {
|
|
1401
|
+
const defaultRoles$2 = options.roles ? Object.keys(options.roles) : [
|
|
1402
|
+
"owner",
|
|
1403
|
+
"admin",
|
|
1404
|
+
"member"
|
|
1405
|
+
];
|
|
1406
|
+
if (defaultRoles$2.includes(role)) {
|
|
1407
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name "${role}" is already taken by a pre-defined role.`, {
|
|
1408
|
+
role,
|
|
1409
|
+
organizationId,
|
|
1410
|
+
defaultRoles: defaultRoles$2
|
|
1411
|
+
});
|
|
1412
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NAME_IS_ALREADY_TAKEN });
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
async function checkIfRoleNameIsTakenByRoleInDB({ organizationId, role, ctx }) {
|
|
1416
|
+
if (await ctx.context.adapter.findOne({
|
|
1417
|
+
model: "organizationRole",
|
|
1418
|
+
where: [{
|
|
1419
|
+
field: "organizationId",
|
|
1420
|
+
value: organizationId,
|
|
1421
|
+
operator: "eq",
|
|
1422
|
+
connector: "AND"
|
|
1423
|
+
}, {
|
|
1424
|
+
field: "role",
|
|
1425
|
+
value: role,
|
|
1426
|
+
operator: "eq",
|
|
1427
|
+
connector: "AND"
|
|
1428
|
+
}]
|
|
1429
|
+
})) {
|
|
1430
|
+
ctx.context.logger.error(`[Dynamic Access Control] The role name "${role}" is already taken by a role in the database.`, {
|
|
1431
|
+
role,
|
|
1432
|
+
organizationId
|
|
1433
|
+
});
|
|
1434
|
+
throw new APIError$1("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ROLE_NAME_IS_ALREADY_TAKEN });
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
//#endregion
|
|
1439
|
+
//#region src/plugins/organization/schema.ts
|
|
1440
|
+
const roleSchema = z.string();
|
|
1441
|
+
const invitationStatus = z.enum([
|
|
1442
|
+
"pending",
|
|
1443
|
+
"accepted",
|
|
1444
|
+
"rejected",
|
|
1445
|
+
"canceled"
|
|
1446
|
+
]).default("pending");
|
|
1447
|
+
const organizationSchema = z.object({
|
|
1448
|
+
id: z.string().default(generateId),
|
|
1449
|
+
name: z.string(),
|
|
1450
|
+
slug: z.string(),
|
|
1451
|
+
logo: z.string().nullish().optional(),
|
|
1452
|
+
metadata: z.record(z.string(), z.unknown()).or(z.string().transform((v) => JSON.parse(v))).optional(),
|
|
1453
|
+
createdAt: z.date()
|
|
1454
|
+
});
|
|
1455
|
+
const memberSchema = z.object({
|
|
1456
|
+
id: z.string().default(generateId),
|
|
1457
|
+
organizationId: z.string(),
|
|
1458
|
+
userId: z.coerce.string(),
|
|
1459
|
+
role: roleSchema,
|
|
1460
|
+
createdAt: z.date().default(() => /* @__PURE__ */ new Date())
|
|
1461
|
+
});
|
|
1462
|
+
const invitationSchema = z.object({
|
|
1463
|
+
id: z.string().default(generateId),
|
|
1464
|
+
organizationId: z.string(),
|
|
1465
|
+
email: z.string(),
|
|
1466
|
+
role: roleSchema,
|
|
1467
|
+
status: invitationStatus,
|
|
1468
|
+
teamId: z.string().nullish(),
|
|
1469
|
+
inviterId: z.string(),
|
|
1470
|
+
expiresAt: z.date(),
|
|
1471
|
+
createdAt: z.date().default(() => /* @__PURE__ */ new Date())
|
|
1472
|
+
});
|
|
1473
|
+
const teamSchema = z.object({
|
|
1474
|
+
id: z.string().default(generateId),
|
|
1475
|
+
name: z.string().min(1),
|
|
1476
|
+
organizationId: z.string(),
|
|
1477
|
+
createdAt: z.date(),
|
|
1478
|
+
updatedAt: z.date().optional()
|
|
1479
|
+
});
|
|
1480
|
+
const teamMemberSchema = z.object({
|
|
1481
|
+
id: z.string().default(generateId),
|
|
1482
|
+
teamId: z.string(),
|
|
1483
|
+
userId: z.string(),
|
|
1484
|
+
createdAt: z.date().default(() => /* @__PURE__ */ new Date())
|
|
1485
|
+
});
|
|
1486
|
+
const organizationRoleSchema = z.object({
|
|
1487
|
+
id: z.string().default(generateId),
|
|
1488
|
+
organizationId: z.string(),
|
|
1489
|
+
role: z.string(),
|
|
1490
|
+
permission: z.record(z.string(), z.array(z.string())),
|
|
1491
|
+
createdAt: z.date().default(() => /* @__PURE__ */ new Date()),
|
|
1492
|
+
updatedAt: z.date().optional()
|
|
1493
|
+
});
|
|
1494
|
+
const defaultRoles$1 = [
|
|
1495
|
+
"admin",
|
|
1496
|
+
"member",
|
|
1497
|
+
"owner"
|
|
1498
|
+
];
|
|
1499
|
+
const defaultRolesSchema = z.union([z.enum(defaultRoles$1), z.array(z.enum(defaultRoles$1))]);
|
|
1500
|
+
|
|
1501
|
+
//#endregion
|
|
1502
|
+
//#region src/plugins/organization/routes/crud-invites.ts
|
|
1503
|
+
const createInvitation = (option) => {
|
|
1504
|
+
const additionalFieldsSchema = toZodSchema({
|
|
1505
|
+
fields: option?.schema?.invitation?.additionalFields || {},
|
|
1506
|
+
isClientSide: true
|
|
1507
|
+
});
|
|
1508
|
+
const baseSchema = z.object({
|
|
1509
|
+
email: z.string().meta({ description: "The email address of the user to invite" }),
|
|
1510
|
+
role: z.union([z.string().meta({ description: "The role to assign to the user" }), z.array(z.string().meta({ description: "The roles to assign to the user" }))]).meta({ description: "The role(s) to assign to the user. It can be `admin`, `member`, owner. Eg: \"member\"" }),
|
|
1511
|
+
organizationId: z.string().meta({ description: "The organization ID to invite the user to" }).optional(),
|
|
1512
|
+
resend: z.boolean().meta({ description: "Resend the invitation email, if the user is already invited. Eg: true" }).optional(),
|
|
1513
|
+
teamId: z.union([z.string().meta({ description: "The team ID to invite the user to" }).optional(), z.array(z.string()).meta({ description: "The team IDs to invite the user to" }).optional()])
|
|
1514
|
+
});
|
|
1515
|
+
return createAuthEndpoint("/organization/invite-member", {
|
|
1516
|
+
method: "POST",
|
|
1517
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
1518
|
+
body: z.object({
|
|
1519
|
+
...baseSchema.shape,
|
|
1520
|
+
...additionalFieldsSchema.shape
|
|
1521
|
+
}),
|
|
1522
|
+
metadata: {
|
|
1523
|
+
$Infer: { body: {} },
|
|
1524
|
+
openapi: {
|
|
1525
|
+
description: "Invite a user to an organization",
|
|
1526
|
+
responses: { "200": {
|
|
1527
|
+
description: "Success",
|
|
1528
|
+
content: { "application/json": { schema: {
|
|
1529
|
+
type: "object",
|
|
1530
|
+
properties: {
|
|
1531
|
+
id: { type: "string" },
|
|
1532
|
+
email: { type: "string" },
|
|
1533
|
+
role: { type: "string" },
|
|
1534
|
+
organizationId: { type: "string" },
|
|
1535
|
+
inviterId: { type: "string" },
|
|
1536
|
+
status: { type: "string" },
|
|
1537
|
+
expiresAt: { type: "string" },
|
|
1538
|
+
createdAt: { type: "string" }
|
|
1539
|
+
},
|
|
1540
|
+
required: [
|
|
1541
|
+
"id",
|
|
1542
|
+
"email",
|
|
1543
|
+
"role",
|
|
1544
|
+
"organizationId",
|
|
1545
|
+
"inviterId",
|
|
1546
|
+
"status",
|
|
1547
|
+
"expiresAt",
|
|
1548
|
+
"createdAt"
|
|
1549
|
+
]
|
|
1550
|
+
} } }
|
|
1551
|
+
} }
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}, async (ctx) => {
|
|
1555
|
+
const session = ctx.context.session;
|
|
1556
|
+
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
|
|
1557
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
1558
|
+
const email = ctx.body.email.toLowerCase();
|
|
1559
|
+
if (!z.string().email().safeParse(email).success) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL });
|
|
1560
|
+
const adapter = getOrgAdapter(ctx.context, option);
|
|
1561
|
+
const member = await adapter.findMemberByOrgId({
|
|
1562
|
+
userId: session.user.id,
|
|
1563
|
+
organizationId
|
|
1564
|
+
});
|
|
1565
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
1566
|
+
if (!await hasPermission({
|
|
1567
|
+
role: member.role,
|
|
1568
|
+
options: ctx.context.orgOptions,
|
|
1569
|
+
permissions: { invitation: ["create"] },
|
|
1570
|
+
organizationId
|
|
1571
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION });
|
|
1572
|
+
const creatorRole = ctx.context.orgOptions.creatorRole || "owner";
|
|
1573
|
+
const roles = parseRoles(ctx.body.role);
|
|
1574
|
+
if (member.role !== creatorRole && roles.split(",").includes(creatorRole)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE });
|
|
1575
|
+
if (await adapter.findMemberByEmail({
|
|
1576
|
+
email,
|
|
1577
|
+
organizationId
|
|
1578
|
+
})) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
1579
|
+
const alreadyInvited = await adapter.findPendingInvitation({
|
|
1580
|
+
email,
|
|
1581
|
+
organizationId
|
|
1582
|
+
});
|
|
1583
|
+
if (alreadyInvited.length && !ctx.body.resend) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION });
|
|
1584
|
+
const organization$1 = await adapter.findOrganizationById(organizationId);
|
|
1585
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
1586
|
+
if (alreadyInvited.length && ctx.body.resend) {
|
|
1587
|
+
const existingInvitation = alreadyInvited[0];
|
|
1588
|
+
const newExpiresAt = getDate(ctx.context.orgOptions.invitationExpiresIn || 3600 * 48, "sec");
|
|
1589
|
+
await ctx.context.adapter.update({
|
|
1590
|
+
model: "invitation",
|
|
1591
|
+
where: [{
|
|
1592
|
+
field: "id",
|
|
1593
|
+
value: existingInvitation.id
|
|
1594
|
+
}],
|
|
1595
|
+
update: { expiresAt: newExpiresAt }
|
|
1596
|
+
});
|
|
1597
|
+
const updatedInvitation = {
|
|
1598
|
+
...existingInvitation,
|
|
1599
|
+
expiresAt: newExpiresAt
|
|
1600
|
+
};
|
|
1601
|
+
await ctx.context.orgOptions.sendInvitationEmail?.({
|
|
1602
|
+
id: updatedInvitation.id,
|
|
1603
|
+
role: updatedInvitation.role,
|
|
1604
|
+
email: updatedInvitation.email.toLowerCase(),
|
|
1605
|
+
organization: organization$1,
|
|
1606
|
+
inviter: {
|
|
1607
|
+
...member,
|
|
1608
|
+
user: session.user
|
|
1609
|
+
},
|
|
1610
|
+
invitation: updatedInvitation
|
|
1611
|
+
}, ctx.request);
|
|
1612
|
+
return ctx.json(updatedInvitation);
|
|
1613
|
+
}
|
|
1614
|
+
if (alreadyInvited.length && ctx.context.orgOptions.cancelPendingInvitationsOnReInvite) await adapter.updateInvitation({
|
|
1615
|
+
invitationId: alreadyInvited[0].id,
|
|
1616
|
+
status: "canceled"
|
|
1617
|
+
});
|
|
1618
|
+
const invitationLimit = typeof ctx.context.orgOptions.invitationLimit === "function" ? await ctx.context.orgOptions.invitationLimit({
|
|
1619
|
+
user: session.user,
|
|
1620
|
+
organization: organization$1,
|
|
1621
|
+
member
|
|
1622
|
+
}, ctx.context) : ctx.context.orgOptions.invitationLimit ?? 100;
|
|
1623
|
+
if ((await adapter.findPendingInvitations({ organizationId })).length >= invitationLimit) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.INVITATION_LIMIT_REACHED });
|
|
1624
|
+
if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== "undefined" && "teamId" in ctx.body && ctx.body.teamId) {
|
|
1625
|
+
const teamIds$1 = typeof ctx.body.teamId === "string" ? [ctx.body.teamId] : ctx.body.teamId;
|
|
1626
|
+
for (const teamId of teamIds$1) {
|
|
1627
|
+
const team = await adapter.findTeamById({
|
|
1628
|
+
teamId,
|
|
1629
|
+
organizationId,
|
|
1630
|
+
includeTeamMembers: true
|
|
1631
|
+
});
|
|
1632
|
+
if (!team) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
1633
|
+
const maximumMembersPerTeam = typeof ctx.context.orgOptions.teams.maximumMembersPerTeam === "function" ? await ctx.context.orgOptions.teams.maximumMembersPerTeam({
|
|
1634
|
+
teamId,
|
|
1635
|
+
session,
|
|
1636
|
+
organizationId
|
|
1637
|
+
}) : ctx.context.orgOptions.teams.maximumMembersPerTeam;
|
|
1638
|
+
if (team.members.length >= maximumMembersPerTeam) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.TEAM_MEMBER_LIMIT_REACHED });
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
const teamIds = "teamId" in ctx.body ? typeof ctx.body.teamId === "string" ? [ctx.body.teamId] : ctx.body.teamId ?? [] : [];
|
|
1642
|
+
const { email: _, role: __, organizationId: ___, resend: ____,...additionalFields } = ctx.body;
|
|
1643
|
+
let invitationData = {
|
|
1644
|
+
role: roles,
|
|
1645
|
+
email,
|
|
1646
|
+
organizationId,
|
|
1647
|
+
teamIds,
|
|
1648
|
+
...additionalFields ? additionalFields : {}
|
|
1649
|
+
};
|
|
1650
|
+
if (option?.organizationHooks?.beforeCreateInvitation) {
|
|
1651
|
+
const response = await option?.organizationHooks.beforeCreateInvitation({
|
|
1652
|
+
invitation: {
|
|
1653
|
+
...invitationData,
|
|
1654
|
+
inviterId: session.user.id,
|
|
1655
|
+
teamId: teamIds.length > 0 ? teamIds[0] : void 0
|
|
1656
|
+
},
|
|
1657
|
+
inviter: session.user,
|
|
1658
|
+
organization: organization$1
|
|
1659
|
+
});
|
|
1660
|
+
if (response && typeof response === "object" && "data" in response) invitationData = {
|
|
1661
|
+
...invitationData,
|
|
1662
|
+
...response.data
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
const invitation = await adapter.createInvitation({
|
|
1666
|
+
invitation: invitationData,
|
|
1667
|
+
user: session.user
|
|
1668
|
+
});
|
|
1669
|
+
await ctx.context.orgOptions.sendInvitationEmail?.({
|
|
1670
|
+
id: invitation.id,
|
|
1671
|
+
role: invitation.role,
|
|
1672
|
+
email: invitation.email.toLowerCase(),
|
|
1673
|
+
organization: organization$1,
|
|
1674
|
+
inviter: {
|
|
1675
|
+
...member,
|
|
1676
|
+
user: session.user
|
|
1677
|
+
},
|
|
1678
|
+
invitation
|
|
1679
|
+
}, ctx.request);
|
|
1680
|
+
if (option?.organizationHooks?.afterCreateInvitation) await option?.organizationHooks.afterCreateInvitation({
|
|
1681
|
+
invitation,
|
|
1682
|
+
inviter: session.user,
|
|
1683
|
+
organization: organization$1
|
|
1684
|
+
});
|
|
1685
|
+
return ctx.json(invitation);
|
|
1686
|
+
});
|
|
1687
|
+
};
|
|
1688
|
+
const acceptInvitation = (options) => createAuthEndpoint("/organization/accept-invitation", {
|
|
1689
|
+
method: "POST",
|
|
1690
|
+
body: z.object({ invitationId: z.string().meta({ description: "The ID of the invitation to accept" }) }),
|
|
1691
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
1692
|
+
metadata: { openapi: {
|
|
1693
|
+
description: "Accept an invitation to an organization",
|
|
1694
|
+
responses: { "200": {
|
|
1695
|
+
description: "Success",
|
|
1696
|
+
content: { "application/json": { schema: {
|
|
1697
|
+
type: "object",
|
|
1698
|
+
properties: {
|
|
1699
|
+
invitation: { type: "object" },
|
|
1700
|
+
member: { type: "object" }
|
|
1701
|
+
}
|
|
1702
|
+
} } }
|
|
1703
|
+
} }
|
|
1704
|
+
} }
|
|
1705
|
+
}, async (ctx) => {
|
|
1706
|
+
const session = ctx.context.session;
|
|
1707
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
1708
|
+
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
|
|
1709
|
+
if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== "pending") throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND });
|
|
1710
|
+
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION });
|
|
1711
|
+
if (ctx.context.orgOptions.requireEmailVerificationOnInvitation && !session.user.emailVerified) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION });
|
|
1712
|
+
const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;
|
|
1713
|
+
if (await adapter.countMembers({ organizationId: invitation.organizationId }) >= membershipLimit) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_MEMBERSHIP_LIMIT_REACHED });
|
|
1714
|
+
const organization$1 = await adapter.findOrganizationById(invitation.organizationId);
|
|
1715
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
1716
|
+
if (options?.organizationHooks?.beforeAcceptInvitation) await options?.organizationHooks.beforeAcceptInvitation({
|
|
1717
|
+
invitation,
|
|
1718
|
+
user: session.user,
|
|
1719
|
+
organization: organization$1
|
|
1720
|
+
});
|
|
1721
|
+
const acceptedI = await adapter.updateInvitation({
|
|
1722
|
+
invitationId: ctx.body.invitationId,
|
|
1723
|
+
status: "accepted"
|
|
1724
|
+
});
|
|
1725
|
+
if (!acceptedI) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.FAILED_TO_RETRIEVE_INVITATION });
|
|
1726
|
+
if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && "teamId" in acceptedI && acceptedI.teamId) {
|
|
1727
|
+
const teamIds = acceptedI.teamId.split(",");
|
|
1728
|
+
const onlyOne = teamIds.length === 1;
|
|
1729
|
+
for (const teamId of teamIds) {
|
|
1730
|
+
await adapter.findOrCreateTeamMember({
|
|
1731
|
+
teamId,
|
|
1732
|
+
userId: session.user.id
|
|
1733
|
+
});
|
|
1734
|
+
if (typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== "undefined") {
|
|
1735
|
+
if (await adapter.countTeamMembers({ teamId }) >= (typeof ctx.context.orgOptions.teams.maximumMembersPerTeam === "function" ? await ctx.context.orgOptions.teams.maximumMembersPerTeam({
|
|
1736
|
+
teamId,
|
|
1737
|
+
session,
|
|
1738
|
+
organizationId: invitation.organizationId
|
|
1739
|
+
}) : ctx.context.orgOptions.teams.maximumMembersPerTeam)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.TEAM_MEMBER_LIMIT_REACHED });
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
if (onlyOne) {
|
|
1743
|
+
const teamId = teamIds[0];
|
|
1744
|
+
await setSessionCookie(ctx, {
|
|
1745
|
+
session: await adapter.setActiveTeam(session.session.token, teamId, ctx),
|
|
1746
|
+
user: session.user
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const member = await adapter.createMember({
|
|
1751
|
+
organizationId: invitation.organizationId,
|
|
1752
|
+
userId: session.user.id,
|
|
1753
|
+
role: invitation.role,
|
|
1754
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1755
|
+
});
|
|
1756
|
+
await adapter.setActiveOrganization(session.session.token, invitation.organizationId, ctx);
|
|
1757
|
+
if (!acceptedI) return ctx.json(null, {
|
|
1758
|
+
status: 400,
|
|
1759
|
+
body: { message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND }
|
|
1760
|
+
});
|
|
1761
|
+
if (options?.organizationHooks?.afterAcceptInvitation) await options?.organizationHooks.afterAcceptInvitation({
|
|
1762
|
+
invitation: acceptedI,
|
|
1763
|
+
member,
|
|
1764
|
+
user: session.user,
|
|
1765
|
+
organization: organization$1
|
|
1766
|
+
});
|
|
1767
|
+
return ctx.json({
|
|
1768
|
+
invitation: acceptedI,
|
|
1769
|
+
member
|
|
1770
|
+
});
|
|
1771
|
+
});
|
|
1772
|
+
const rejectInvitation = (options) => createAuthEndpoint("/organization/reject-invitation", {
|
|
1773
|
+
method: "POST",
|
|
1774
|
+
body: z.object({ invitationId: z.string().meta({ description: "The ID of the invitation to reject" }) }),
|
|
1775
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
1776
|
+
metadata: { openapi: {
|
|
1777
|
+
description: "Reject an invitation to an organization",
|
|
1778
|
+
responses: { "200": {
|
|
1779
|
+
description: "Success",
|
|
1780
|
+
content: { "application/json": { schema: {
|
|
1781
|
+
type: "object",
|
|
1782
|
+
properties: {
|
|
1783
|
+
invitation: { type: "object" },
|
|
1784
|
+
member: { type: "null" }
|
|
1785
|
+
}
|
|
1786
|
+
} } }
|
|
1787
|
+
} }
|
|
1788
|
+
} }
|
|
1789
|
+
}, async (ctx) => {
|
|
1790
|
+
const session = ctx.context.session;
|
|
1791
|
+
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
|
1792
|
+
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
|
|
1793
|
+
if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== "pending") throw new APIError("BAD_REQUEST", { message: "Invitation not found!" });
|
|
1794
|
+
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION });
|
|
1795
|
+
if (ctx.context.orgOptions.requireEmailVerificationOnInvitation && !session.user.emailVerified) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION });
|
|
1796
|
+
const organization$1 = await adapter.findOrganizationById(invitation.organizationId);
|
|
1797
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
1798
|
+
if (options?.organizationHooks?.beforeRejectInvitation) await options?.organizationHooks.beforeRejectInvitation({
|
|
1799
|
+
invitation,
|
|
1800
|
+
user: session.user,
|
|
1801
|
+
organization: organization$1
|
|
1802
|
+
});
|
|
1803
|
+
const rejectedI = await adapter.updateInvitation({
|
|
1804
|
+
invitationId: ctx.body.invitationId,
|
|
1805
|
+
status: "rejected"
|
|
1806
|
+
});
|
|
1807
|
+
if (options?.organizationHooks?.afterRejectInvitation) await options?.organizationHooks.afterRejectInvitation({
|
|
1808
|
+
invitation: rejectedI || invitation,
|
|
1809
|
+
user: session.user,
|
|
1810
|
+
organization: organization$1
|
|
1811
|
+
});
|
|
1812
|
+
return ctx.json({
|
|
1813
|
+
invitation: rejectedI,
|
|
1814
|
+
member: null
|
|
1815
|
+
});
|
|
1816
|
+
});
|
|
1817
|
+
const cancelInvitation = (options) => createAuthEndpoint("/organization/cancel-invitation", {
|
|
1818
|
+
method: "POST",
|
|
1819
|
+
body: z.object({ invitationId: z.string().meta({ description: "The ID of the invitation to cancel" }) }),
|
|
1820
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
1821
|
+
openapi: {
|
|
1822
|
+
description: "Cancel an invitation to an organization",
|
|
1823
|
+
responses: { "200": {
|
|
1824
|
+
description: "Success",
|
|
1825
|
+
content: { "application/json": { schema: {
|
|
1826
|
+
type: "object",
|
|
1827
|
+
properties: { invitation: { type: "object" } }
|
|
1828
|
+
} } }
|
|
1829
|
+
} }
|
|
1830
|
+
}
|
|
1831
|
+
}, async (ctx) => {
|
|
1832
|
+
const session = ctx.context.session;
|
|
1833
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
1834
|
+
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
|
|
1835
|
+
if (!invitation) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND });
|
|
1836
|
+
const member = await adapter.findMemberByOrgId({
|
|
1837
|
+
userId: session.user.id,
|
|
1838
|
+
organizationId: invitation.organizationId
|
|
1839
|
+
});
|
|
1840
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
1841
|
+
if (!await hasPermission({
|
|
1842
|
+
role: member.role,
|
|
1843
|
+
options: ctx.context.orgOptions,
|
|
1844
|
+
permissions: { invitation: ["cancel"] },
|
|
1845
|
+
organizationId: invitation.organizationId
|
|
1846
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION });
|
|
1847
|
+
const organization$1 = await adapter.findOrganizationById(invitation.organizationId);
|
|
1848
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
1849
|
+
if (options?.organizationHooks?.beforeCancelInvitation) await options?.organizationHooks.beforeCancelInvitation({
|
|
1850
|
+
invitation,
|
|
1851
|
+
cancelledBy: session.user,
|
|
1852
|
+
organization: organization$1
|
|
1853
|
+
});
|
|
1854
|
+
const canceledI = await adapter.updateInvitation({
|
|
1855
|
+
invitationId: ctx.body.invitationId,
|
|
1856
|
+
status: "canceled"
|
|
1857
|
+
});
|
|
1858
|
+
if (options?.organizationHooks?.afterCancelInvitation) await options?.organizationHooks.afterCancelInvitation({
|
|
1859
|
+
invitation: canceledI || invitation,
|
|
1860
|
+
cancelledBy: session.user,
|
|
1861
|
+
organization: organization$1
|
|
1862
|
+
});
|
|
1863
|
+
return ctx.json(canceledI);
|
|
1864
|
+
});
|
|
1865
|
+
const getInvitation = (options) => createAuthEndpoint("/organization/get-invitation", {
|
|
1866
|
+
method: "GET",
|
|
1867
|
+
use: [orgMiddleware],
|
|
1868
|
+
requireHeaders: true,
|
|
1869
|
+
query: z.object({ id: z.string().meta({ description: "The ID of the invitation to get" }) }),
|
|
1870
|
+
metadata: { openapi: {
|
|
1871
|
+
description: "Get an invitation by ID",
|
|
1872
|
+
responses: { "200": {
|
|
1873
|
+
description: "Success",
|
|
1874
|
+
content: { "application/json": { schema: {
|
|
1875
|
+
type: "object",
|
|
1876
|
+
properties: {
|
|
1877
|
+
id: { type: "string" },
|
|
1878
|
+
email: { type: "string" },
|
|
1879
|
+
role: { type: "string" },
|
|
1880
|
+
organizationId: { type: "string" },
|
|
1881
|
+
inviterId: { type: "string" },
|
|
1882
|
+
status: { type: "string" },
|
|
1883
|
+
expiresAt: { type: "string" },
|
|
1884
|
+
organizationName: { type: "string" },
|
|
1885
|
+
organizationSlug: { type: "string" },
|
|
1886
|
+
inviterEmail: { type: "string" }
|
|
1887
|
+
},
|
|
1888
|
+
required: [
|
|
1889
|
+
"id",
|
|
1890
|
+
"email",
|
|
1891
|
+
"role",
|
|
1892
|
+
"organizationId",
|
|
1893
|
+
"inviterId",
|
|
1894
|
+
"status",
|
|
1895
|
+
"expiresAt",
|
|
1896
|
+
"organizationName",
|
|
1897
|
+
"organizationSlug",
|
|
1898
|
+
"inviterEmail"
|
|
1899
|
+
]
|
|
1900
|
+
} } }
|
|
1901
|
+
} }
|
|
1902
|
+
} }
|
|
1903
|
+
}, async (ctx) => {
|
|
1904
|
+
const session = await getSessionFromCtx(ctx);
|
|
1905
|
+
if (!session) throw new APIError("UNAUTHORIZED", { message: "Not authenticated" });
|
|
1906
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
1907
|
+
const invitation = await adapter.findInvitationById(ctx.query.id);
|
|
1908
|
+
if (!invitation || invitation.status !== "pending" || invitation.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", { message: "Invitation not found!" });
|
|
1909
|
+
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION });
|
|
1910
|
+
const organization$1 = await adapter.findOrganizationById(invitation.organizationId);
|
|
1911
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
1912
|
+
const member = await adapter.findMemberByOrgId({
|
|
1913
|
+
userId: invitation.inviterId,
|
|
1914
|
+
organizationId: invitation.organizationId
|
|
1915
|
+
});
|
|
1916
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION });
|
|
1917
|
+
return ctx.json({
|
|
1918
|
+
...invitation,
|
|
1919
|
+
organizationName: organization$1.name,
|
|
1920
|
+
organizationSlug: organization$1.slug,
|
|
1921
|
+
inviterEmail: member.user.email
|
|
1922
|
+
});
|
|
1923
|
+
});
|
|
1924
|
+
const listInvitations = (options) => createAuthEndpoint("/organization/list-invitations", {
|
|
1925
|
+
method: "GET",
|
|
1926
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
1927
|
+
query: z.object({ organizationId: z.string().meta({ description: "The ID of the organization to list invitations for" }).optional() }).optional()
|
|
1928
|
+
}, async (ctx) => {
|
|
1929
|
+
const session = await getSessionFromCtx(ctx);
|
|
1930
|
+
if (!session) throw new APIError("UNAUTHORIZED", { message: "Not authenticated" });
|
|
1931
|
+
const orgId = ctx.query?.organizationId || session.session.activeOrganizationId;
|
|
1932
|
+
if (!orgId) throw new APIError("BAD_REQUEST", { message: "Organization ID is required" });
|
|
1933
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
1934
|
+
if (!await adapter.findMemberByOrgId({
|
|
1935
|
+
userId: session.user.id,
|
|
1936
|
+
organizationId: orgId
|
|
1937
|
+
})) throw new APIError("FORBIDDEN", { message: "You are not a member of this organization" });
|
|
1938
|
+
const invitations = await adapter.listInvitations({ organizationId: orgId });
|
|
1939
|
+
return ctx.json(invitations);
|
|
1940
|
+
});
|
|
1941
|
+
/**
|
|
1942
|
+
* List all invitations a user has received
|
|
1943
|
+
*/
|
|
1944
|
+
const listUserInvitations = (options) => createAuthEndpoint("/organization/list-user-invitations", {
|
|
1945
|
+
method: "GET",
|
|
1946
|
+
use: [orgMiddleware],
|
|
1947
|
+
query: z.object({ email: z.string().meta({ description: "The email of the user to list invitations for. This only works for server side API calls." }).optional() }).optional()
|
|
1948
|
+
}, async (ctx) => {
|
|
1949
|
+
const session = await getSessionFromCtx(ctx);
|
|
1950
|
+
if (ctx.request && ctx.query?.email) throw new APIError("BAD_REQUEST", { message: "User email cannot be passed for client side API calls." });
|
|
1951
|
+
const userEmail = session?.user.email || ctx.query?.email;
|
|
1952
|
+
if (!userEmail) throw new APIError("BAD_REQUEST", { message: "Missing session headers, or email query parameter." });
|
|
1953
|
+
const invitations = await getOrgAdapter(ctx.context, options).listUserInvitations(userEmail);
|
|
1954
|
+
return ctx.json(invitations);
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
//#endregion
|
|
1958
|
+
//#region src/plugins/organization/routes/crud-members.ts
|
|
1959
|
+
const addMember = (option) => {
|
|
1960
|
+
const additionalFieldsSchema = toZodSchema({
|
|
1961
|
+
fields: option?.schema?.member?.additionalFields || {},
|
|
1962
|
+
isClientSide: true
|
|
1963
|
+
});
|
|
1964
|
+
const baseSchema = z.object({
|
|
1965
|
+
userId: z.coerce.string().meta({ description: "The user Id which represents the user to be added as a member. If `null` is provided, then it's expected to provide session headers. Eg: \"user-id\"" }),
|
|
1966
|
+
role: z.union([z.string(), z.array(z.string())]).meta({ description: "The role(s) to assign to the new member. Eg: [\"admin\", \"sale\"]" }),
|
|
1967
|
+
organizationId: z.string().meta({ description: "An optional organization ID to pass. If not provided, will default to the user's active organization. Eg: \"org-id\"" }).optional(),
|
|
1968
|
+
teamId: z.string().meta({ description: "An optional team ID to add the member to. Eg: \"team-id\"" }).optional()
|
|
1969
|
+
});
|
|
1970
|
+
return createAuthEndpoint("/organization/add-member", {
|
|
1971
|
+
method: "POST",
|
|
1972
|
+
body: z.object({
|
|
1973
|
+
...baseSchema.shape,
|
|
1974
|
+
...additionalFieldsSchema.shape
|
|
1975
|
+
}),
|
|
1976
|
+
use: [orgMiddleware],
|
|
1977
|
+
metadata: {
|
|
1978
|
+
SERVER_ONLY: true,
|
|
1979
|
+
$Infer: { body: {} }
|
|
1980
|
+
}
|
|
1981
|
+
}, async (ctx) => {
|
|
1982
|
+
const session = ctx.body.userId ? await getSessionFromCtx(ctx).catch((e) => null) : null;
|
|
1983
|
+
const orgId = ctx.body.organizationId || session?.session.activeOrganizationId;
|
|
1984
|
+
if (!orgId) return ctx.json(null, {
|
|
1985
|
+
status: 400,
|
|
1986
|
+
body: { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION }
|
|
1987
|
+
});
|
|
1988
|
+
const teamId = "teamId" in ctx.body ? ctx.body.teamId : void 0;
|
|
1989
|
+
if (teamId && !ctx.context.orgOptions.teams?.enabled) {
|
|
1990
|
+
ctx.context.logger.error("Teams are not enabled");
|
|
1991
|
+
throw new APIError("BAD_REQUEST", { message: "Teams are not enabled" });
|
|
1992
|
+
}
|
|
1993
|
+
const adapter = getOrgAdapter(ctx.context, option);
|
|
1994
|
+
const user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
|
|
1995
|
+
if (!user) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
|
|
1996
|
+
if (await adapter.findMemberByEmail({
|
|
1997
|
+
email: user.email,
|
|
1998
|
+
organizationId: orgId
|
|
1999
|
+
})) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
2000
|
+
if (teamId) {
|
|
2001
|
+
const team = await adapter.findTeamById({
|
|
2002
|
+
teamId,
|
|
2003
|
+
organizationId: orgId
|
|
2004
|
+
});
|
|
2005
|
+
if (!team || team.organizationId !== orgId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
2006
|
+
}
|
|
2007
|
+
const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;
|
|
2008
|
+
if (await adapter.countMembers({ organizationId: orgId }) >= membershipLimit) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_MEMBERSHIP_LIMIT_REACHED });
|
|
2009
|
+
const { role: _, userId: __, organizationId: ___,...additionalFields } = ctx.body;
|
|
2010
|
+
const organization$1 = await adapter.findOrganizationById(orgId);
|
|
2011
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2012
|
+
let memberData = {
|
|
2013
|
+
organizationId: orgId,
|
|
2014
|
+
userId: user.id,
|
|
2015
|
+
role: parseRoles(ctx.body.role),
|
|
2016
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2017
|
+
...additionalFields ? additionalFields : {}
|
|
2018
|
+
};
|
|
2019
|
+
if (option?.organizationHooks?.beforeAddMember) {
|
|
2020
|
+
const response = await option?.organizationHooks.beforeAddMember({
|
|
2021
|
+
member: {
|
|
2022
|
+
userId: user.id,
|
|
2023
|
+
organizationId: orgId,
|
|
2024
|
+
role: parseRoles(ctx.body.role),
|
|
2025
|
+
...additionalFields
|
|
2026
|
+
},
|
|
2027
|
+
user,
|
|
2028
|
+
organization: organization$1
|
|
2029
|
+
});
|
|
2030
|
+
if (response && typeof response === "object" && "data" in response) memberData = {
|
|
2031
|
+
...memberData,
|
|
2032
|
+
...response.data
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
const createdMember = await adapter.createMember(memberData);
|
|
2036
|
+
if (teamId) await adapter.findOrCreateTeamMember({
|
|
2037
|
+
userId: user.id,
|
|
2038
|
+
teamId
|
|
2039
|
+
});
|
|
2040
|
+
if (option?.organizationHooks?.afterAddMember) await option?.organizationHooks.afterAddMember({
|
|
2041
|
+
member: createdMember,
|
|
2042
|
+
user,
|
|
2043
|
+
organization: organization$1
|
|
2044
|
+
});
|
|
2045
|
+
return ctx.json(createdMember);
|
|
2046
|
+
});
|
|
2047
|
+
};
|
|
2048
|
+
const removeMember = (options) => createAuthEndpoint("/organization/remove-member", {
|
|
2049
|
+
method: "POST",
|
|
2050
|
+
body: z.object({
|
|
2051
|
+
memberIdOrEmail: z.string().meta({ description: "The ID or email of the member to remove" }),
|
|
2052
|
+
organizationId: z.string().meta({ description: "The ID of the organization to remove the member from. If not provided, the active organization will be used. Eg: \"org-id\"" }).optional()
|
|
2053
|
+
}),
|
|
2054
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
2055
|
+
metadata: { openapi: {
|
|
2056
|
+
description: "Remove a member from an organization",
|
|
2057
|
+
responses: { "200": {
|
|
2058
|
+
description: "Success",
|
|
2059
|
+
content: { "application/json": { schema: {
|
|
2060
|
+
type: "object",
|
|
2061
|
+
properties: { member: {
|
|
2062
|
+
type: "object",
|
|
2063
|
+
properties: {
|
|
2064
|
+
id: { type: "string" },
|
|
2065
|
+
userId: { type: "string" },
|
|
2066
|
+
organizationId: { type: "string" },
|
|
2067
|
+
role: { type: "string" }
|
|
2068
|
+
},
|
|
2069
|
+
required: [
|
|
2070
|
+
"id",
|
|
2071
|
+
"userId",
|
|
2072
|
+
"organizationId",
|
|
2073
|
+
"role"
|
|
2074
|
+
]
|
|
2075
|
+
} },
|
|
2076
|
+
required: ["member"]
|
|
2077
|
+
} } }
|
|
2078
|
+
} }
|
|
2079
|
+
} }
|
|
2080
|
+
}, async (ctx) => {
|
|
2081
|
+
const session = ctx.context.session;
|
|
2082
|
+
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
|
|
2083
|
+
if (!organizationId) return ctx.json(null, {
|
|
2084
|
+
status: 400,
|
|
2085
|
+
body: { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION }
|
|
2086
|
+
});
|
|
2087
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2088
|
+
const member = await adapter.findMemberByOrgId({
|
|
2089
|
+
userId: session.user.id,
|
|
2090
|
+
organizationId
|
|
2091
|
+
});
|
|
2092
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2093
|
+
let toBeRemovedMember = null;
|
|
2094
|
+
if (ctx.body.memberIdOrEmail.includes("@")) toBeRemovedMember = await adapter.findMemberByEmail({
|
|
2095
|
+
email: ctx.body.memberIdOrEmail,
|
|
2096
|
+
organizationId
|
|
2097
|
+
});
|
|
2098
|
+
else toBeRemovedMember = await adapter.findMemberById(ctx.body.memberIdOrEmail);
|
|
2099
|
+
if (!toBeRemovedMember) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2100
|
+
const roles = toBeRemovedMember.role.split(",");
|
|
2101
|
+
const creatorRole = ctx.context.orgOptions?.creatorRole || "owner";
|
|
2102
|
+
if (roles.includes(creatorRole)) {
|
|
2103
|
+
if (member.role !== creatorRole) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER });
|
|
2104
|
+
const { members } = await adapter.listMembers({ organizationId });
|
|
2105
|
+
if (members.filter((member$1) => {
|
|
2106
|
+
return member$1.role.split(",").includes(creatorRole);
|
|
2107
|
+
}).length <= 1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER });
|
|
2108
|
+
}
|
|
2109
|
+
if (!await hasPermission({
|
|
2110
|
+
role: member.role,
|
|
2111
|
+
options: ctx.context.orgOptions,
|
|
2112
|
+
permissions: { member: ["delete"] },
|
|
2113
|
+
organizationId
|
|
2114
|
+
}, ctx)) throw new APIError("UNAUTHORIZED", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER });
|
|
2115
|
+
if (toBeRemovedMember?.organizationId !== organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2116
|
+
const organization$1 = await adapter.findOrganizationById(organizationId);
|
|
2117
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2118
|
+
const userBeingRemoved = await ctx.context.internalAdapter.findUserById(toBeRemovedMember.userId);
|
|
2119
|
+
if (!userBeingRemoved) throw new APIError("BAD_REQUEST", { message: "User not found" });
|
|
2120
|
+
if (options?.organizationHooks?.beforeRemoveMember) await options?.organizationHooks.beforeRemoveMember({
|
|
2121
|
+
member: toBeRemovedMember,
|
|
2122
|
+
user: userBeingRemoved,
|
|
2123
|
+
organization: organization$1
|
|
2124
|
+
});
|
|
2125
|
+
await adapter.deleteMember({
|
|
2126
|
+
memberId: toBeRemovedMember.id,
|
|
2127
|
+
organizationId,
|
|
2128
|
+
userId: toBeRemovedMember.userId
|
|
2129
|
+
});
|
|
2130
|
+
if (session.user.id === toBeRemovedMember.userId && session.session.activeOrganizationId === toBeRemovedMember.organizationId) await adapter.setActiveOrganization(session.session.token, null, ctx);
|
|
2131
|
+
if (options?.organizationHooks?.afterRemoveMember) await options?.organizationHooks.afterRemoveMember({
|
|
2132
|
+
member: toBeRemovedMember,
|
|
2133
|
+
user: userBeingRemoved,
|
|
2134
|
+
organization: organization$1
|
|
2135
|
+
});
|
|
2136
|
+
return ctx.json({ member: toBeRemovedMember });
|
|
2137
|
+
});
|
|
2138
|
+
const updateMemberRole = (option) => createAuthEndpoint("/organization/update-member-role", {
|
|
2139
|
+
method: "POST",
|
|
2140
|
+
body: z.object({
|
|
2141
|
+
role: z.union([z.string(), z.array(z.string())]).meta({ description: "The new role to be applied. This can be a string or array of strings representing the roles. Eg: [\"admin\", \"sale\"]" }),
|
|
2142
|
+
memberId: z.string().meta({ description: "The member id to apply the role update to. Eg: \"member-id\"" }),
|
|
2143
|
+
organizationId: z.string().meta({ description: "An optional organization ID which the member is a part of to apply the role update. If not provided, you must provide session headers to get the active organization. Eg: \"organization-id\"" }).optional()
|
|
2144
|
+
}),
|
|
2145
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
2146
|
+
metadata: {
|
|
2147
|
+
$Infer: { body: {} },
|
|
2148
|
+
openapi: {
|
|
2149
|
+
description: "Update the role of a member in an organization",
|
|
2150
|
+
responses: { "200": {
|
|
2151
|
+
description: "Success",
|
|
2152
|
+
content: { "application/json": { schema: {
|
|
2153
|
+
type: "object",
|
|
2154
|
+
properties: { member: {
|
|
2155
|
+
type: "object",
|
|
2156
|
+
properties: {
|
|
2157
|
+
id: { type: "string" },
|
|
2158
|
+
userId: { type: "string" },
|
|
2159
|
+
organizationId: { type: "string" },
|
|
2160
|
+
role: { type: "string" }
|
|
2161
|
+
},
|
|
2162
|
+
required: [
|
|
2163
|
+
"id",
|
|
2164
|
+
"userId",
|
|
2165
|
+
"organizationId",
|
|
2166
|
+
"role"
|
|
2167
|
+
]
|
|
2168
|
+
} },
|
|
2169
|
+
required: ["member"]
|
|
2170
|
+
} } }
|
|
2171
|
+
} }
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
}, async (ctx) => {
|
|
2175
|
+
const session = ctx.context.session;
|
|
2176
|
+
if (!ctx.body.role) throw new APIError("BAD_REQUEST");
|
|
2177
|
+
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
|
|
2178
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
2179
|
+
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
|
2180
|
+
const roleToSet = Array.isArray(ctx.body.role) ? ctx.body.role : ctx.body.role ? [ctx.body.role] : [];
|
|
2181
|
+
const member = await adapter.findMemberByOrgId({
|
|
2182
|
+
userId: session.user.id,
|
|
2183
|
+
organizationId
|
|
2184
|
+
});
|
|
2185
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2186
|
+
const toBeUpdatedMember = member.id !== ctx.body.memberId ? await adapter.findMemberById(ctx.body.memberId) : member;
|
|
2187
|
+
if (!toBeUpdatedMember) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2188
|
+
if (!(toBeUpdatedMember.organizationId === organizationId)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER });
|
|
2189
|
+
const creatorRole = ctx.context.orgOptions?.creatorRole || "owner";
|
|
2190
|
+
const updatingMemberRoles = member.role.split(",");
|
|
2191
|
+
const isUpdatingCreator = toBeUpdatedMember.role.split(",").includes(creatorRole);
|
|
2192
|
+
const updaterIsCreator = updatingMemberRoles.includes(creatorRole);
|
|
2193
|
+
const isSettingCreatorRole = roleToSet.includes(creatorRole);
|
|
2194
|
+
const memberIsUpdatingThemselves = member.id === toBeUpdatedMember.id;
|
|
2195
|
+
if (isUpdatingCreator && !updaterIsCreator || isSettingCreatorRole && !updaterIsCreator) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER });
|
|
2196
|
+
if (updaterIsCreator && memberIsUpdatingThemselves) {
|
|
2197
|
+
if ((await ctx.context.adapter.findMany({
|
|
2198
|
+
model: "member",
|
|
2199
|
+
where: [{
|
|
2200
|
+
field: "organizationId",
|
|
2201
|
+
value: organizationId
|
|
2202
|
+
}]
|
|
2203
|
+
})).filter((member$1) => {
|
|
2204
|
+
return member$1.role.split(",").includes(creatorRole);
|
|
2205
|
+
}).length <= 1 && !isSettingCreatorRole) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER });
|
|
2206
|
+
}
|
|
2207
|
+
if (!await hasPermission({
|
|
2208
|
+
role: member.role,
|
|
2209
|
+
options: ctx.context.orgOptions,
|
|
2210
|
+
permissions: { member: ["update"] },
|
|
2211
|
+
allowCreatorAllPermissions: true,
|
|
2212
|
+
organizationId
|
|
2213
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER });
|
|
2214
|
+
const organization$1 = await adapter.findOrganizationById(organizationId);
|
|
2215
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2216
|
+
const userBeingUpdated = await ctx.context.internalAdapter.findUserById(toBeUpdatedMember.userId);
|
|
2217
|
+
if (!userBeingUpdated) throw new APIError("BAD_REQUEST", { message: "User not found" });
|
|
2218
|
+
const previousRole = toBeUpdatedMember.role;
|
|
2219
|
+
const newRole = parseRoles(ctx.body.role);
|
|
2220
|
+
if (option?.organizationHooks?.beforeUpdateMemberRole) {
|
|
2221
|
+
const response = await option?.organizationHooks.beforeUpdateMemberRole({
|
|
2222
|
+
member: toBeUpdatedMember,
|
|
2223
|
+
newRole,
|
|
2224
|
+
user: userBeingUpdated,
|
|
2225
|
+
organization: organization$1
|
|
2226
|
+
});
|
|
2227
|
+
if (response && typeof response === "object" && "data" in response) {
|
|
2228
|
+
const updatedMember$1 = await adapter.updateMember(ctx.body.memberId, response.data.role || newRole);
|
|
2229
|
+
if (!updatedMember$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2230
|
+
if (option?.organizationHooks?.afterUpdateMemberRole) await option?.organizationHooks.afterUpdateMemberRole({
|
|
2231
|
+
member: updatedMember$1,
|
|
2232
|
+
previousRole,
|
|
2233
|
+
user: userBeingUpdated,
|
|
2234
|
+
organization: organization$1
|
|
2235
|
+
});
|
|
2236
|
+
return ctx.json(updatedMember$1);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
const updatedMember = await adapter.updateMember(ctx.body.memberId, newRole);
|
|
2240
|
+
if (!updatedMember) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2241
|
+
if (option?.organizationHooks?.afterUpdateMemberRole) await option?.organizationHooks.afterUpdateMemberRole({
|
|
2242
|
+
member: updatedMember,
|
|
2243
|
+
previousRole,
|
|
2244
|
+
user: userBeingUpdated,
|
|
2245
|
+
organization: organization$1
|
|
2246
|
+
});
|
|
2247
|
+
return ctx.json(updatedMember);
|
|
2248
|
+
});
|
|
2249
|
+
const getActiveMember = (options) => createAuthEndpoint("/organization/get-active-member", {
|
|
2250
|
+
method: "GET",
|
|
2251
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
2252
|
+
requireHeaders: true,
|
|
2253
|
+
metadata: { openapi: {
|
|
2254
|
+
description: "Get the member details of the active organization",
|
|
2255
|
+
responses: { "200": {
|
|
2256
|
+
description: "Success",
|
|
2257
|
+
content: { "application/json": { schema: {
|
|
2258
|
+
type: "object",
|
|
2259
|
+
properties: {
|
|
2260
|
+
id: { type: "string" },
|
|
2261
|
+
userId: { type: "string" },
|
|
2262
|
+
organizationId: { type: "string" },
|
|
2263
|
+
role: { type: "string" }
|
|
2264
|
+
},
|
|
2265
|
+
required: [
|
|
2266
|
+
"id",
|
|
2267
|
+
"userId",
|
|
2268
|
+
"organizationId",
|
|
2269
|
+
"role"
|
|
2270
|
+
]
|
|
2271
|
+
} } }
|
|
2272
|
+
} }
|
|
2273
|
+
} }
|
|
2274
|
+
}, async (ctx) => {
|
|
2275
|
+
const session = ctx.context.session;
|
|
2276
|
+
const organizationId = session.session.activeOrganizationId;
|
|
2277
|
+
if (!organizationId) return ctx.json(null, {
|
|
2278
|
+
status: 400,
|
|
2279
|
+
body: { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION }
|
|
2280
|
+
});
|
|
2281
|
+
const member = await getOrgAdapter(ctx.context, options).findMemberByOrgId({
|
|
2282
|
+
userId: session.user.id,
|
|
2283
|
+
organizationId
|
|
2284
|
+
});
|
|
2285
|
+
if (!member) return ctx.json(null, {
|
|
2286
|
+
status: 400,
|
|
2287
|
+
body: { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND }
|
|
2288
|
+
});
|
|
2289
|
+
return ctx.json(member);
|
|
2290
|
+
});
|
|
2291
|
+
const leaveOrganization = (options) => createAuthEndpoint("/organization/leave", {
|
|
2292
|
+
method: "POST",
|
|
2293
|
+
body: z.object({ organizationId: z.string().meta({ description: "The organization Id for the member to leave. Eg: \"organization-id\"" }) }),
|
|
2294
|
+
requireHeaders: true,
|
|
2295
|
+
use: [sessionMiddleware, orgMiddleware]
|
|
2296
|
+
}, async (ctx) => {
|
|
2297
|
+
const session = ctx.context.session;
|
|
2298
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2299
|
+
const member = await adapter.findMemberByOrgId({
|
|
2300
|
+
userId: session.user.id,
|
|
2301
|
+
organizationId: ctx.body.organizationId
|
|
2302
|
+
});
|
|
2303
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.MEMBER_NOT_FOUND });
|
|
2304
|
+
const creatorRole = ctx.context.orgOptions?.creatorRole || "owner";
|
|
2305
|
+
if (member.role.split(",").includes(creatorRole)) {
|
|
2306
|
+
if ((await ctx.context.adapter.findMany({
|
|
2307
|
+
model: "member",
|
|
2308
|
+
where: [{
|
|
2309
|
+
field: "organizationId",
|
|
2310
|
+
value: ctx.body.organizationId
|
|
2311
|
+
}]
|
|
2312
|
+
})).filter((member$1) => member$1.role.split(",").includes(creatorRole)).length <= 1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER });
|
|
2313
|
+
}
|
|
2314
|
+
await adapter.deleteMember({
|
|
2315
|
+
memberId: member.id,
|
|
2316
|
+
organizationId: ctx.body.organizationId,
|
|
2317
|
+
userId: session.user.id
|
|
2318
|
+
});
|
|
2319
|
+
if (session.session.activeOrganizationId === ctx.body.organizationId) await adapter.setActiveOrganization(session.session.token, null, ctx);
|
|
2320
|
+
return ctx.json(member);
|
|
2321
|
+
});
|
|
2322
|
+
const listMembers = (options) => createAuthEndpoint("/organization/list-members", {
|
|
2323
|
+
method: "GET",
|
|
2324
|
+
query: z.object({
|
|
2325
|
+
limit: z.string().meta({ description: "The number of users to return" }).or(z.number()).optional(),
|
|
2326
|
+
offset: z.string().meta({ description: "The offset to start from" }).or(z.number()).optional(),
|
|
2327
|
+
sortBy: z.string().meta({ description: "The field to sort by" }).optional(),
|
|
2328
|
+
sortDirection: z.enum(["asc", "desc"]).meta({ description: "The direction to sort by" }).optional(),
|
|
2329
|
+
filterField: z.string().meta({ description: "The field to filter by" }).optional(),
|
|
2330
|
+
filterValue: z.string().meta({ description: "The value to filter by" }).or(z.number()).or(z.boolean()).optional(),
|
|
2331
|
+
filterOperator: z.enum([
|
|
2332
|
+
"eq",
|
|
2333
|
+
"ne",
|
|
2334
|
+
"lt",
|
|
2335
|
+
"lte",
|
|
2336
|
+
"gt",
|
|
2337
|
+
"gte",
|
|
2338
|
+
"contains"
|
|
2339
|
+
]).meta({ description: "The operator to use for the filter" }).optional(),
|
|
2340
|
+
organizationId: z.string().meta({ description: "The organization ID to list members for. If not provided, will default to the user's active organization. Eg: \"organization-id\"" }).optional()
|
|
2341
|
+
}).optional(),
|
|
2342
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
2343
|
+
}, async (ctx) => {
|
|
2344
|
+
const session = ctx.context.session;
|
|
2345
|
+
const organizationId = ctx.query?.organizationId || session.session.activeOrganizationId;
|
|
2346
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
2347
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2348
|
+
if (!await adapter.findMemberByOrgId({
|
|
2349
|
+
userId: session.user.id,
|
|
2350
|
+
organizationId
|
|
2351
|
+
})) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
2352
|
+
const { members, total } = await adapter.listMembers({
|
|
2353
|
+
organizationId,
|
|
2354
|
+
limit: ctx.query?.limit ? Number(ctx.query.limit) : void 0,
|
|
2355
|
+
offset: ctx.query?.offset ? Number(ctx.query.offset) : void 0,
|
|
2356
|
+
sortBy: ctx.query?.sortBy,
|
|
2357
|
+
sortOrder: ctx.query?.sortDirection,
|
|
2358
|
+
filter: ctx.query?.filterField ? {
|
|
2359
|
+
field: ctx.query?.filterField,
|
|
2360
|
+
operator: ctx.query.filterOperator,
|
|
2361
|
+
value: ctx.query.filterValue
|
|
2362
|
+
} : void 0
|
|
2363
|
+
});
|
|
2364
|
+
return ctx.json({
|
|
2365
|
+
members,
|
|
2366
|
+
total
|
|
2367
|
+
});
|
|
2368
|
+
});
|
|
2369
|
+
const getActiveMemberRole = (options) => createAuthEndpoint("/organization/get-active-member-role", {
|
|
2370
|
+
method: "GET",
|
|
2371
|
+
query: z.object({
|
|
2372
|
+
userId: z.string().meta({ description: "The user ID to get the role for. If not provided, will default to the current user's" }).optional(),
|
|
2373
|
+
organizationId: z.string().meta({ description: "The organization ID to list members for. If not provided, will default to the user's active organization. Eg: \"organization-id\"" }).optional()
|
|
2374
|
+
}).optional(),
|
|
2375
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
2376
|
+
}, async (ctx) => {
|
|
2377
|
+
const session = ctx.context.session;
|
|
2378
|
+
const organizationId = ctx.query?.organizationId || session.session.activeOrganizationId;
|
|
2379
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
2380
|
+
const userId = ctx.query?.userId || session.user.id;
|
|
2381
|
+
const member = await getOrgAdapter(ctx.context, options).findMemberByOrgId({
|
|
2382
|
+
userId,
|
|
2383
|
+
organizationId
|
|
2384
|
+
});
|
|
2385
|
+
if (!member) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION });
|
|
2386
|
+
return ctx.json({ role: member?.role });
|
|
2387
|
+
});
|
|
2388
|
+
|
|
2389
|
+
//#endregion
|
|
2390
|
+
//#region src/plugins/organization/routes/crud-org.ts
|
|
2391
|
+
const createOrganization = (options) => {
|
|
2392
|
+
const additionalFieldsSchema = toZodSchema({
|
|
2393
|
+
fields: options?.schema?.organization?.additionalFields || {},
|
|
2394
|
+
isClientSide: true
|
|
2395
|
+
});
|
|
2396
|
+
const baseSchema = z.object({
|
|
2397
|
+
name: z.string().min(1).meta({ description: "The name of the organization" }),
|
|
2398
|
+
slug: z.string().min(1).meta({ description: "The slug of the organization" }),
|
|
2399
|
+
userId: z.coerce.string().meta({ description: "The user id of the organization creator. If not provided, the current user will be used. Should only be used by admins or when called by the server. server-only. Eg: \"user-id\"" }).optional(),
|
|
2400
|
+
logo: z.string().meta({ description: "The logo of the organization" }).optional(),
|
|
2401
|
+
metadata: z.record(z.string(), z.any()).meta({ description: "The metadata of the organization" }).optional(),
|
|
2402
|
+
keepCurrentActiveOrganization: z.boolean().meta({ description: "Whether to keep the current active organization active after creating a new one. Eg: true" }).optional()
|
|
2403
|
+
});
|
|
2404
|
+
return createAuthEndpoint("/organization/create", {
|
|
2405
|
+
method: "POST",
|
|
2406
|
+
body: z.object({
|
|
2407
|
+
...baseSchema.shape,
|
|
2408
|
+
...additionalFieldsSchema.shape
|
|
2409
|
+
}),
|
|
2410
|
+
use: [orgMiddleware],
|
|
2411
|
+
metadata: {
|
|
2412
|
+
$Infer: { body: {} },
|
|
2413
|
+
openapi: {
|
|
2414
|
+
description: "Create an organization",
|
|
2415
|
+
responses: { "200": {
|
|
2416
|
+
description: "Success",
|
|
2417
|
+
content: { "application/json": { schema: {
|
|
2418
|
+
type: "object",
|
|
2419
|
+
description: "The organization that was created",
|
|
2420
|
+
$ref: "#/components/schemas/Organization"
|
|
2421
|
+
} } }
|
|
2422
|
+
} }
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}, async (ctx) => {
|
|
2426
|
+
const session = await getSessionFromCtx(ctx);
|
|
2427
|
+
if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED");
|
|
2428
|
+
let user = session?.user || null;
|
|
2429
|
+
if (!user) {
|
|
2430
|
+
if (!ctx.body.userId) throw new APIError("UNAUTHORIZED");
|
|
2431
|
+
user = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
|
|
2432
|
+
}
|
|
2433
|
+
if (!user) return ctx.json(null, { status: 401 });
|
|
2434
|
+
const options$1 = ctx.context.orgOptions;
|
|
2435
|
+
if (!(typeof options$1?.allowUserToCreateOrganization === "function" ? await options$1.allowUserToCreateOrganization(user) : options$1?.allowUserToCreateOrganization === void 0 ? true : options$1.allowUserToCreateOrganization)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION });
|
|
2436
|
+
const adapter = getOrgAdapter(ctx.context, options$1);
|
|
2437
|
+
const userOrganizations = await adapter.listOrganizations(user.id);
|
|
2438
|
+
if (typeof options$1.organizationLimit === "number" ? userOrganizations.length >= options$1.organizationLimit : typeof options$1.organizationLimit === "function" ? await options$1.organizationLimit(user) : false) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS });
|
|
2439
|
+
if (await adapter.findOrganizationBySlug(ctx.body.slug)) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_ALREADY_EXISTS });
|
|
2440
|
+
let { keepCurrentActiveOrganization: _, userId: __,...orgData } = ctx.body;
|
|
2441
|
+
if (options$1.organizationCreation?.beforeCreate) {
|
|
2442
|
+
const response = await options$1.organizationCreation.beforeCreate({
|
|
2443
|
+
organization: {
|
|
2444
|
+
...orgData,
|
|
2445
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2446
|
+
},
|
|
2447
|
+
user
|
|
2448
|
+
}, ctx.request);
|
|
2449
|
+
if (response && typeof response === "object" && "data" in response) orgData = {
|
|
2450
|
+
...ctx.body,
|
|
2451
|
+
...response.data
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
if (options$1?.organizationHooks?.beforeCreateOrganization) {
|
|
2455
|
+
const response = await options$1?.organizationHooks.beforeCreateOrganization({
|
|
2456
|
+
organization: orgData,
|
|
2457
|
+
user
|
|
2458
|
+
});
|
|
2459
|
+
if (response && typeof response === "object" && "data" in response) orgData = {
|
|
2460
|
+
...ctx.body,
|
|
2461
|
+
...response.data
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
const organization$1 = await adapter.createOrganization({ organization: {
|
|
2465
|
+
...orgData,
|
|
2466
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2467
|
+
} });
|
|
2468
|
+
let member;
|
|
2469
|
+
let teamMember = null;
|
|
2470
|
+
let data = {
|
|
2471
|
+
userId: user.id,
|
|
2472
|
+
organizationId: organization$1.id,
|
|
2473
|
+
role: ctx.context.orgOptions.creatorRole || "owner"
|
|
2474
|
+
};
|
|
2475
|
+
if (options$1?.organizationHooks?.beforeAddMember) {
|
|
2476
|
+
const response = await options$1?.organizationHooks.beforeAddMember({
|
|
2477
|
+
member: {
|
|
2478
|
+
userId: user.id,
|
|
2479
|
+
organizationId: organization$1.id,
|
|
2480
|
+
role: ctx.context.orgOptions.creatorRole || "owner"
|
|
2481
|
+
},
|
|
2482
|
+
user,
|
|
2483
|
+
organization: organization$1
|
|
2484
|
+
});
|
|
2485
|
+
if (response && typeof response === "object" && "data" in response) data = {
|
|
2486
|
+
...data,
|
|
2487
|
+
...response.data
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
member = await adapter.createMember(data);
|
|
2491
|
+
if (options$1?.organizationHooks?.afterAddMember) await options$1?.organizationHooks.afterAddMember({
|
|
2492
|
+
member,
|
|
2493
|
+
user,
|
|
2494
|
+
organization: organization$1
|
|
2495
|
+
});
|
|
2496
|
+
if (options$1?.teams?.enabled && options$1.teams.defaultTeam?.enabled !== false) {
|
|
2497
|
+
let teamData = {
|
|
2498
|
+
organizationId: organization$1.id,
|
|
2499
|
+
name: `${organization$1.name}`,
|
|
2500
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
2501
|
+
};
|
|
2502
|
+
if (options$1?.organizationHooks?.beforeCreateTeam) {
|
|
2503
|
+
const response = await options$1?.organizationHooks.beforeCreateTeam({
|
|
2504
|
+
team: {
|
|
2505
|
+
organizationId: organization$1.id,
|
|
2506
|
+
name: `${organization$1.name}`
|
|
2507
|
+
},
|
|
2508
|
+
user,
|
|
2509
|
+
organization: organization$1
|
|
2510
|
+
});
|
|
2511
|
+
if (response && typeof response === "object" && "data" in response) teamData = {
|
|
2512
|
+
...teamData,
|
|
2513
|
+
...response.data
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
const defaultTeam = await options$1.teams.defaultTeam?.customCreateDefaultTeam?.(organization$1, ctx.request) || await adapter.createTeam(teamData);
|
|
2517
|
+
teamMember = await adapter.findOrCreateTeamMember({
|
|
2518
|
+
teamId: defaultTeam.id,
|
|
2519
|
+
userId: user.id
|
|
2520
|
+
});
|
|
2521
|
+
if (options$1?.organizationHooks?.afterCreateTeam) await options$1?.organizationHooks.afterCreateTeam({
|
|
2522
|
+
team: defaultTeam,
|
|
2523
|
+
user,
|
|
2524
|
+
organization: organization$1
|
|
2525
|
+
});
|
|
2526
|
+
}
|
|
2527
|
+
if (options$1.organizationCreation?.afterCreate) await options$1.organizationCreation.afterCreate({
|
|
2528
|
+
organization: organization$1,
|
|
2529
|
+
user,
|
|
2530
|
+
member
|
|
2531
|
+
}, ctx.request);
|
|
2532
|
+
if (options$1?.organizationHooks?.afterCreateOrganization) await options$1?.organizationHooks.afterCreateOrganization({
|
|
2533
|
+
organization: organization$1,
|
|
2534
|
+
user,
|
|
2535
|
+
member
|
|
2536
|
+
});
|
|
2537
|
+
if (ctx.context.session && !ctx.body.keepCurrentActiveOrganization) await adapter.setActiveOrganization(ctx.context.session.session.token, organization$1.id, ctx);
|
|
2538
|
+
if (teamMember && ctx.context.session && !ctx.body.keepCurrentActiveOrganization) await adapter.setActiveTeam(ctx.context.session.session.token, teamMember.teamId, ctx);
|
|
2539
|
+
return ctx.json({
|
|
2540
|
+
...organization$1,
|
|
2541
|
+
metadata: organization$1.metadata && typeof organization$1.metadata === "string" ? JSON.parse(organization$1.metadata) : organization$1.metadata,
|
|
2542
|
+
members: [member]
|
|
2543
|
+
});
|
|
2544
|
+
});
|
|
2545
|
+
};
|
|
2546
|
+
const checkOrganizationSlug = (options) => createAuthEndpoint("/organization/check-slug", {
|
|
2547
|
+
method: "POST",
|
|
2548
|
+
body: z.object({ slug: z.string().meta({ description: "The organization slug to check. Eg: \"my-org\"" }) }),
|
|
2549
|
+
use: [requestOnlySessionMiddleware, orgMiddleware]
|
|
2550
|
+
}, async (ctx) => {
|
|
2551
|
+
if (!await getOrgAdapter(ctx.context, options).findOrganizationBySlug(ctx.body.slug)) return ctx.json({ status: true });
|
|
2552
|
+
throw new APIError("BAD_REQUEST", { message: "slug is taken" });
|
|
2553
|
+
});
|
|
2554
|
+
const updateOrganization = (options) => {
|
|
2555
|
+
const additionalFieldsSchema = toZodSchema({
|
|
2556
|
+
fields: options?.schema?.organization?.additionalFields || {},
|
|
2557
|
+
isClientSide: true
|
|
2558
|
+
});
|
|
2559
|
+
return createAuthEndpoint("/organization/update", {
|
|
2560
|
+
method: "POST",
|
|
2561
|
+
body: z.object({
|
|
2562
|
+
data: z.object({
|
|
2563
|
+
...additionalFieldsSchema.shape,
|
|
2564
|
+
name: z.string().min(1).meta({ description: "The name of the organization" }).optional(),
|
|
2565
|
+
slug: z.string().min(1).meta({ description: "The slug of the organization" }).optional(),
|
|
2566
|
+
logo: z.string().meta({ description: "The logo of the organization" }).optional(),
|
|
2567
|
+
metadata: z.record(z.string(), z.any()).meta({ description: "The metadata of the organization" }).optional()
|
|
2568
|
+
}).partial(),
|
|
2569
|
+
organizationId: z.string().meta({ description: "The organization ID. Eg: \"org-id\"" }).optional()
|
|
2570
|
+
}),
|
|
2571
|
+
requireHeaders: true,
|
|
2572
|
+
use: [orgMiddleware],
|
|
2573
|
+
metadata: {
|
|
2574
|
+
$Infer: { body: {} },
|
|
2575
|
+
openapi: {
|
|
2576
|
+
description: "Update an organization",
|
|
2577
|
+
responses: { "200": {
|
|
2578
|
+
description: "Success",
|
|
2579
|
+
content: { "application/json": { schema: {
|
|
2580
|
+
type: "object",
|
|
2581
|
+
description: "The updated organization",
|
|
2582
|
+
$ref: "#/components/schemas/Organization"
|
|
2583
|
+
} } }
|
|
2584
|
+
} }
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
}, async (ctx) => {
|
|
2588
|
+
const session = await ctx.context.getSession(ctx);
|
|
2589
|
+
if (!session) throw new APIError("UNAUTHORIZED", { message: "User not found" });
|
|
2590
|
+
const organizationId = ctx.body.organizationId || session.session.activeOrganizationId;
|
|
2591
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2592
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2593
|
+
const member = await adapter.findMemberByOrgId({
|
|
2594
|
+
userId: session.user.id,
|
|
2595
|
+
organizationId
|
|
2596
|
+
});
|
|
2597
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
2598
|
+
if (!await hasPermission({
|
|
2599
|
+
permissions: { organization: ["update"] },
|
|
2600
|
+
role: member.role,
|
|
2601
|
+
options: ctx.context.orgOptions,
|
|
2602
|
+
organizationId
|
|
2603
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION });
|
|
2604
|
+
if (typeof ctx.body.data.slug === "string") {
|
|
2605
|
+
const existingOrganization = await adapter.findOrganizationBySlug(ctx.body.data.slug);
|
|
2606
|
+
if (existingOrganization && existingOrganization.id !== organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_SLUG_ALREADY_TAKEN });
|
|
2607
|
+
}
|
|
2608
|
+
if (options?.organizationHooks?.beforeUpdateOrganization) {
|
|
2609
|
+
const response = await options.organizationHooks.beforeUpdateOrganization({
|
|
2610
|
+
organization: ctx.body.data,
|
|
2611
|
+
user: session.user,
|
|
2612
|
+
member
|
|
2613
|
+
});
|
|
2614
|
+
if (response && typeof response === "object" && "data" in response) ctx.body.data = {
|
|
2615
|
+
...ctx.body.data,
|
|
2616
|
+
...response.data
|
|
2617
|
+
};
|
|
2618
|
+
}
|
|
2619
|
+
const updatedOrg = await adapter.updateOrganization(organizationId, ctx.body.data);
|
|
2620
|
+
if (options?.organizationHooks?.afterUpdateOrganization) await options.organizationHooks.afterUpdateOrganization({
|
|
2621
|
+
organization: updatedOrg,
|
|
2622
|
+
user: session.user,
|
|
2623
|
+
member
|
|
2624
|
+
});
|
|
2625
|
+
return ctx.json(updatedOrg);
|
|
2626
|
+
});
|
|
2627
|
+
};
|
|
2628
|
+
const deleteOrganization = (options) => {
|
|
2629
|
+
return createAuthEndpoint("/organization/delete", {
|
|
2630
|
+
method: "POST",
|
|
2631
|
+
body: z.object({ organizationId: z.string().meta({ description: "The organization id to delete" }) }),
|
|
2632
|
+
requireHeaders: true,
|
|
2633
|
+
use: [orgMiddleware],
|
|
2634
|
+
metadata: { openapi: {
|
|
2635
|
+
description: "Delete an organization",
|
|
2636
|
+
responses: { "200": {
|
|
2637
|
+
description: "Success",
|
|
2638
|
+
content: { "application/json": { schema: {
|
|
2639
|
+
type: "string",
|
|
2640
|
+
description: "The organization id that was deleted"
|
|
2641
|
+
} } }
|
|
2642
|
+
} }
|
|
2643
|
+
} }
|
|
2644
|
+
}, async (ctx) => {
|
|
2645
|
+
if (ctx.context.orgOptions.organizationDeletion?.disabled || ctx.context.orgOptions.disableOrganizationDeletion) {
|
|
2646
|
+
if (ctx.context.orgOptions.organizationDeletion?.disabled) ctx.context.logger.info("`organizationDeletion.disabled` is deprecated. Use `disableOrganizationDeletion` instead");
|
|
2647
|
+
throw new APIError("NOT_FOUND", { message: "Organization deletion is disabled" });
|
|
2648
|
+
}
|
|
2649
|
+
const session = await ctx.context.getSession(ctx);
|
|
2650
|
+
if (!session) throw new APIError("UNAUTHORIZED", { status: 401 });
|
|
2651
|
+
const organizationId = ctx.body.organizationId;
|
|
2652
|
+
if (!organizationId) return ctx.json(null, {
|
|
2653
|
+
status: 400,
|
|
2654
|
+
body: { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND }
|
|
2655
|
+
});
|
|
2656
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2657
|
+
const member = await adapter.findMemberByOrgId({
|
|
2658
|
+
userId: session.user.id,
|
|
2659
|
+
organizationId
|
|
2660
|
+
});
|
|
2661
|
+
if (!member) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
2662
|
+
if (!await hasPermission({
|
|
2663
|
+
role: member.role,
|
|
2664
|
+
permissions: { organization: ["delete"] },
|
|
2665
|
+
organizationId,
|
|
2666
|
+
options: ctx.context.orgOptions
|
|
2667
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION });
|
|
2668
|
+
if (organizationId === session.session.activeOrganizationId)
|
|
2669
|
+
/**
|
|
2670
|
+
* If the organization is deleted, we set the active organization to null
|
|
2671
|
+
*/
|
|
2672
|
+
await adapter.setActiveOrganization(session.session.token, null, ctx);
|
|
2673
|
+
const org = await adapter.findOrganizationById(organizationId);
|
|
2674
|
+
if (!org) throw new APIError("BAD_REQUEST");
|
|
2675
|
+
if (options?.organizationHooks?.beforeDeleteOrganization) await options.organizationHooks.beforeDeleteOrganization({
|
|
2676
|
+
organization: org,
|
|
2677
|
+
user: session.user
|
|
2678
|
+
});
|
|
2679
|
+
await adapter.deleteOrganization(organizationId);
|
|
2680
|
+
if (options?.organizationHooks?.afterDeleteOrganization) await options.organizationHooks.afterDeleteOrganization({
|
|
2681
|
+
organization: org,
|
|
2682
|
+
user: session.user
|
|
2683
|
+
});
|
|
2684
|
+
return ctx.json(org);
|
|
2685
|
+
});
|
|
2686
|
+
};
|
|
2687
|
+
const getFullOrganization = (options) => createAuthEndpoint("/organization/get-full-organization", {
|
|
2688
|
+
method: "GET",
|
|
2689
|
+
query: z.optional(z.object({
|
|
2690
|
+
organizationId: z.string().meta({ description: "The organization id to get" }).optional(),
|
|
2691
|
+
organizationSlug: z.string().meta({ description: "The organization slug to get" }).optional(),
|
|
2692
|
+
membersLimit: z.number().or(z.string().transform((val) => parseInt(val))).meta({ description: "The limit of members to get. By default, it uses the membershipLimit option which defaults to 100." }).optional()
|
|
2693
|
+
})),
|
|
2694
|
+
requireHeaders: true,
|
|
2695
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
2696
|
+
metadata: { openapi: {
|
|
2697
|
+
description: "Get the full organization",
|
|
2698
|
+
responses: { "200": {
|
|
2699
|
+
description: "Success",
|
|
2700
|
+
content: { "application/json": { schema: {
|
|
2701
|
+
type: "object",
|
|
2702
|
+
description: "The organization",
|
|
2703
|
+
$ref: "#/components/schemas/Organization"
|
|
2704
|
+
} } }
|
|
2705
|
+
} }
|
|
2706
|
+
} }
|
|
2707
|
+
}, async (ctx) => {
|
|
2708
|
+
const session = ctx.context.session;
|
|
2709
|
+
const organizationId = ctx.query?.organizationSlug || ctx.query?.organizationId || session.session.activeOrganizationId;
|
|
2710
|
+
if (!organizationId) return ctx.json(null, { status: 200 });
|
|
2711
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2712
|
+
const organization$1 = await adapter.findFullOrganization({
|
|
2713
|
+
organizationId,
|
|
2714
|
+
isSlug: !!ctx.query?.organizationSlug,
|
|
2715
|
+
includeTeams: ctx.context.orgOptions.teams?.enabled,
|
|
2716
|
+
membersLimit: ctx.query?.membersLimit
|
|
2717
|
+
});
|
|
2718
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2719
|
+
if (!await adapter.checkMembership({
|
|
2720
|
+
userId: session.user.id,
|
|
2721
|
+
organizationId: organization$1.id
|
|
2722
|
+
})) {
|
|
2723
|
+
await adapter.setActiveOrganization(session.session.token, null, ctx);
|
|
2724
|
+
throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
2725
|
+
}
|
|
2726
|
+
return ctx.json(organization$1);
|
|
2727
|
+
});
|
|
2728
|
+
const setActiveOrganization = (options) => {
|
|
2729
|
+
return createAuthEndpoint("/organization/set-active", {
|
|
2730
|
+
method: "POST",
|
|
2731
|
+
body: z.object({
|
|
2732
|
+
organizationId: z.string().meta({ description: "The organization id to set as active. It can be null to unset the active organization. Eg: \"org-id\"" }).nullable().optional(),
|
|
2733
|
+
organizationSlug: z.string().meta({ description: "The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided. Eg: \"org-slug\"" }).optional()
|
|
2734
|
+
}),
|
|
2735
|
+
use: [orgSessionMiddleware, orgMiddleware],
|
|
2736
|
+
metadata: { openapi: {
|
|
2737
|
+
description: "Set the active organization",
|
|
2738
|
+
responses: { "200": {
|
|
2739
|
+
description: "Success",
|
|
2740
|
+
content: { "application/json": { schema: {
|
|
2741
|
+
type: "object",
|
|
2742
|
+
description: "The organization",
|
|
2743
|
+
$ref: "#/components/schemas/Organization"
|
|
2744
|
+
} } }
|
|
2745
|
+
} }
|
|
2746
|
+
} }
|
|
2747
|
+
}, async (ctx) => {
|
|
2748
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2749
|
+
const session = ctx.context.session;
|
|
2750
|
+
let organizationId = ctx.body.organizationId;
|
|
2751
|
+
let organizationSlug = ctx.body.organizationSlug;
|
|
2752
|
+
if (organizationId === null) {
|
|
2753
|
+
if (!session.session.activeOrganizationId) return ctx.json(null);
|
|
2754
|
+
await setSessionCookie(ctx, {
|
|
2755
|
+
session: await adapter.setActiveOrganization(session.session.token, null, ctx),
|
|
2756
|
+
user: session.user
|
|
2757
|
+
});
|
|
2758
|
+
return ctx.json(null);
|
|
2759
|
+
}
|
|
2760
|
+
if (!organizationId && !organizationSlug) {
|
|
2761
|
+
const sessionOrgId = session.session.activeOrganizationId;
|
|
2762
|
+
if (!sessionOrgId) return ctx.json(null);
|
|
2763
|
+
organizationId = sessionOrgId;
|
|
2764
|
+
}
|
|
2765
|
+
if (organizationSlug && !organizationId) {
|
|
2766
|
+
const organization$2 = await adapter.findOrganizationBySlug(organizationSlug);
|
|
2767
|
+
if (!organization$2) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2768
|
+
organizationId = organization$2.id;
|
|
2769
|
+
}
|
|
2770
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2771
|
+
if (!await adapter.checkMembership({
|
|
2772
|
+
userId: session.user.id,
|
|
2773
|
+
organizationId
|
|
2774
|
+
})) {
|
|
2775
|
+
await adapter.setActiveOrganization(session.session.token, null, ctx);
|
|
2776
|
+
throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
2777
|
+
}
|
|
2778
|
+
let organization$1 = await adapter.findOrganizationById(organizationId);
|
|
2779
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2780
|
+
await setSessionCookie(ctx, {
|
|
2781
|
+
session: await adapter.setActiveOrganization(session.session.token, organization$1.id, ctx),
|
|
2782
|
+
user: session.user
|
|
2783
|
+
});
|
|
2784
|
+
return ctx.json(organization$1);
|
|
2785
|
+
});
|
|
2786
|
+
};
|
|
2787
|
+
const listOrganizations = (options) => createAuthEndpoint("/organization/list", {
|
|
2788
|
+
method: "GET",
|
|
2789
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
2790
|
+
metadata: { openapi: {
|
|
2791
|
+
description: "List all organizations",
|
|
2792
|
+
responses: { "200": {
|
|
2793
|
+
description: "Success",
|
|
2794
|
+
content: { "application/json": { schema: {
|
|
2795
|
+
type: "array",
|
|
2796
|
+
items: { $ref: "#/components/schemas/Organization" }
|
|
2797
|
+
} } }
|
|
2798
|
+
} }
|
|
2799
|
+
} }
|
|
2800
|
+
}, async (ctx) => {
|
|
2801
|
+
const organizations = await getOrgAdapter(ctx.context, options).listOrganizations(ctx.context.session.user.id);
|
|
2802
|
+
return ctx.json(organizations);
|
|
2803
|
+
});
|
|
2804
|
+
|
|
2805
|
+
//#endregion
|
|
2806
|
+
//#region src/plugins/organization/routes/crud-team.ts
|
|
2807
|
+
const createTeam = (options) => {
|
|
2808
|
+
const additionalFieldsSchema = toZodSchema({
|
|
2809
|
+
fields: options?.schema?.team?.additionalFields ?? {},
|
|
2810
|
+
isClientSide: true
|
|
2811
|
+
});
|
|
2812
|
+
const baseSchema = z$1.object({
|
|
2813
|
+
name: z$1.string().meta({ description: "The name of the team. Eg: \"my-team\"" }),
|
|
2814
|
+
organizationId: z$1.string().meta({ description: "The organization ID which the team will be created in. Defaults to the active organization. Eg: \"organization-id\"" }).optional()
|
|
2815
|
+
});
|
|
2816
|
+
return createAuthEndpoint("/organization/create-team", {
|
|
2817
|
+
method: "POST",
|
|
2818
|
+
body: z$1.object({
|
|
2819
|
+
...baseSchema.shape,
|
|
2820
|
+
...additionalFieldsSchema.shape
|
|
2821
|
+
}),
|
|
2822
|
+
use: [orgMiddleware],
|
|
2823
|
+
metadata: {
|
|
2824
|
+
$Infer: { body: {} },
|
|
2825
|
+
openapi: {
|
|
2826
|
+
description: "Create a new team within an organization",
|
|
2827
|
+
responses: { "200": {
|
|
2828
|
+
description: "Team created successfully",
|
|
2829
|
+
content: { "application/json": { schema: {
|
|
2830
|
+
type: "object",
|
|
2831
|
+
properties: {
|
|
2832
|
+
id: {
|
|
2833
|
+
type: "string",
|
|
2834
|
+
description: "Unique identifier of the created team"
|
|
2835
|
+
},
|
|
2836
|
+
name: {
|
|
2837
|
+
type: "string",
|
|
2838
|
+
description: "Name of the team"
|
|
2839
|
+
},
|
|
2840
|
+
organizationId: {
|
|
2841
|
+
type: "string",
|
|
2842
|
+
description: "ID of the organization the team belongs to"
|
|
2843
|
+
},
|
|
2844
|
+
createdAt: {
|
|
2845
|
+
type: "string",
|
|
2846
|
+
format: "date-time",
|
|
2847
|
+
description: "Timestamp when the team was created"
|
|
2848
|
+
},
|
|
2849
|
+
updatedAt: {
|
|
2850
|
+
type: "string",
|
|
2851
|
+
format: "date-time",
|
|
2852
|
+
description: "Timestamp when the team was last updated"
|
|
2853
|
+
}
|
|
2854
|
+
},
|
|
2855
|
+
required: [
|
|
2856
|
+
"id",
|
|
2857
|
+
"name",
|
|
2858
|
+
"organizationId",
|
|
2859
|
+
"createdAt",
|
|
2860
|
+
"updatedAt"
|
|
2861
|
+
]
|
|
2862
|
+
} } }
|
|
2863
|
+
} }
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}, async (ctx) => {
|
|
2867
|
+
const session = await getSessionFromCtx(ctx);
|
|
2868
|
+
const organizationId = ctx.body.organizationId || session?.session.activeOrganizationId;
|
|
2869
|
+
if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED");
|
|
2870
|
+
if (!organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
2871
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2872
|
+
if (session) {
|
|
2873
|
+
const member = await adapter.findMemberByOrgId({
|
|
2874
|
+
userId: session.user.id,
|
|
2875
|
+
organizationId
|
|
2876
|
+
});
|
|
2877
|
+
if (!member) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION });
|
|
2878
|
+
if (!await hasPermission({
|
|
2879
|
+
role: member.role,
|
|
2880
|
+
options: ctx.context.orgOptions,
|
|
2881
|
+
permissions: { team: ["create"] },
|
|
2882
|
+
organizationId
|
|
2883
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION });
|
|
2884
|
+
}
|
|
2885
|
+
const existingTeams = await adapter.listTeams(organizationId);
|
|
2886
|
+
const maximum = typeof ctx.context.orgOptions.teams?.maximumTeams === "function" ? await ctx.context.orgOptions.teams?.maximumTeams({
|
|
2887
|
+
organizationId,
|
|
2888
|
+
session
|
|
2889
|
+
}, ctx.request) : ctx.context.orgOptions.teams?.maximumTeams;
|
|
2890
|
+
if (maximum ? existingTeams.length >= maximum : false) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS });
|
|
2891
|
+
const { name, organizationId: _,...additionalFields } = ctx.body;
|
|
2892
|
+
const organization$1 = await adapter.findOrganizationById(organizationId);
|
|
2893
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2894
|
+
let teamData = {
|
|
2895
|
+
name,
|
|
2896
|
+
organizationId,
|
|
2897
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
2898
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
2899
|
+
...additionalFields
|
|
2900
|
+
};
|
|
2901
|
+
if (options?.organizationHooks?.beforeCreateTeam) {
|
|
2902
|
+
const response = await options?.organizationHooks.beforeCreateTeam({
|
|
2903
|
+
team: {
|
|
2904
|
+
name,
|
|
2905
|
+
organizationId,
|
|
2906
|
+
...additionalFields
|
|
2907
|
+
},
|
|
2908
|
+
user: session?.user,
|
|
2909
|
+
organization: organization$1
|
|
2910
|
+
});
|
|
2911
|
+
if (response && typeof response === "object" && "data" in response) teamData = {
|
|
2912
|
+
...teamData,
|
|
2913
|
+
...response.data
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
const createdTeam = await adapter.createTeam(teamData);
|
|
2917
|
+
if (options?.organizationHooks?.afterCreateTeam) await options?.organizationHooks.afterCreateTeam({
|
|
2918
|
+
team: createdTeam,
|
|
2919
|
+
user: session?.user,
|
|
2920
|
+
organization: organization$1
|
|
2921
|
+
});
|
|
2922
|
+
return ctx.json(createdTeam);
|
|
2923
|
+
});
|
|
2924
|
+
};
|
|
2925
|
+
const removeTeam = (options) => createAuthEndpoint("/organization/remove-team", {
|
|
2926
|
+
method: "POST",
|
|
2927
|
+
body: z$1.object({
|
|
2928
|
+
teamId: z$1.string().meta({ description: `The team ID of the team to remove. Eg: "team-id"` }),
|
|
2929
|
+
organizationId: z$1.string().meta({ description: `The organization ID which the team falls under. If not provided, it will default to the user's active organization. Eg: "organization-id"` }).optional()
|
|
2930
|
+
}),
|
|
2931
|
+
use: [orgMiddleware],
|
|
2932
|
+
metadata: { openapi: {
|
|
2933
|
+
description: "Remove a team from an organization",
|
|
2934
|
+
responses: { "200": {
|
|
2935
|
+
description: "Team removed successfully",
|
|
2936
|
+
content: { "application/json": { schema: {
|
|
2937
|
+
type: "object",
|
|
2938
|
+
properties: { message: {
|
|
2939
|
+
type: "string",
|
|
2940
|
+
description: "Confirmation message indicating successful removal",
|
|
2941
|
+
enum: ["Team removed successfully."]
|
|
2942
|
+
} },
|
|
2943
|
+
required: ["message"]
|
|
2944
|
+
} } }
|
|
2945
|
+
} }
|
|
2946
|
+
} }
|
|
2947
|
+
}, async (ctx) => {
|
|
2948
|
+
const session = await getSessionFromCtx(ctx);
|
|
2949
|
+
const organizationId = ctx.body.organizationId || session?.session.activeOrganizationId;
|
|
2950
|
+
if (!organizationId) return ctx.json(null, {
|
|
2951
|
+
status: 400,
|
|
2952
|
+
body: { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION }
|
|
2953
|
+
});
|
|
2954
|
+
if (!session && (ctx.request || ctx.headers)) throw new APIError("UNAUTHORIZED");
|
|
2955
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
2956
|
+
if (session) {
|
|
2957
|
+
const member = await adapter.findMemberByOrgId({
|
|
2958
|
+
userId: session.user.id,
|
|
2959
|
+
organizationId
|
|
2960
|
+
});
|
|
2961
|
+
if (!member || session.session?.activeTeamId === ctx.body.teamId) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM });
|
|
2962
|
+
if (!await hasPermission({
|
|
2963
|
+
role: member.role,
|
|
2964
|
+
options: ctx.context.orgOptions,
|
|
2965
|
+
permissions: { team: ["delete"] },
|
|
2966
|
+
organizationId
|
|
2967
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION });
|
|
2968
|
+
}
|
|
2969
|
+
const team = await adapter.findTeamById({
|
|
2970
|
+
teamId: ctx.body.teamId,
|
|
2971
|
+
organizationId
|
|
2972
|
+
});
|
|
2973
|
+
if (!team || team.organizationId !== organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
2974
|
+
if (!ctx.context.orgOptions.teams?.allowRemovingAllTeams) {
|
|
2975
|
+
if ((await adapter.listTeams(organizationId)).length <= 1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.UNABLE_TO_REMOVE_LAST_TEAM });
|
|
2976
|
+
}
|
|
2977
|
+
const organization$1 = await adapter.findOrganizationById(organizationId);
|
|
2978
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
2979
|
+
if (options?.organizationHooks?.beforeDeleteTeam) await options?.organizationHooks.beforeDeleteTeam({
|
|
2980
|
+
team,
|
|
2981
|
+
user: session?.user,
|
|
2982
|
+
organization: organization$1
|
|
2983
|
+
});
|
|
2984
|
+
await adapter.deleteTeam(team.id);
|
|
2985
|
+
if (options?.organizationHooks?.afterDeleteTeam) await options?.organizationHooks.afterDeleteTeam({
|
|
2986
|
+
team,
|
|
2987
|
+
user: session?.user,
|
|
2988
|
+
organization: organization$1
|
|
2989
|
+
});
|
|
2990
|
+
return ctx.json({ message: "Team removed successfully." });
|
|
2991
|
+
});
|
|
2992
|
+
const updateTeam = (options) => {
|
|
2993
|
+
const additionalFieldsSchema = toZodSchema({
|
|
2994
|
+
fields: options?.schema?.team?.additionalFields ?? {},
|
|
2995
|
+
isClientSide: true
|
|
2996
|
+
});
|
|
2997
|
+
return createAuthEndpoint("/organization/update-team", {
|
|
2998
|
+
method: "POST",
|
|
2999
|
+
body: z$1.object({
|
|
3000
|
+
teamId: z$1.string().meta({ description: `The ID of the team to be updated. Eg: "team-id"` }),
|
|
3001
|
+
data: z$1.object({
|
|
3002
|
+
...teamSchema.shape,
|
|
3003
|
+
...additionalFieldsSchema.shape
|
|
3004
|
+
}).partial()
|
|
3005
|
+
}),
|
|
3006
|
+
requireHeaders: true,
|
|
3007
|
+
use: [orgMiddleware, orgSessionMiddleware],
|
|
3008
|
+
metadata: {
|
|
3009
|
+
$Infer: { body: {} },
|
|
3010
|
+
openapi: {
|
|
3011
|
+
description: "Update an existing team in an organization",
|
|
3012
|
+
responses: { "200": {
|
|
3013
|
+
description: "Team updated successfully",
|
|
3014
|
+
content: { "application/json": { schema: {
|
|
3015
|
+
type: "object",
|
|
3016
|
+
properties: {
|
|
3017
|
+
id: {
|
|
3018
|
+
type: "string",
|
|
3019
|
+
description: "Unique identifier of the updated team"
|
|
3020
|
+
},
|
|
3021
|
+
name: {
|
|
3022
|
+
type: "string",
|
|
3023
|
+
description: "Updated name of the team"
|
|
3024
|
+
},
|
|
3025
|
+
organizationId: {
|
|
3026
|
+
type: "string",
|
|
3027
|
+
description: "ID of the organization the team belongs to"
|
|
3028
|
+
},
|
|
3029
|
+
createdAt: {
|
|
3030
|
+
type: "string",
|
|
3031
|
+
format: "date-time",
|
|
3032
|
+
description: "Timestamp when the team was created"
|
|
3033
|
+
},
|
|
3034
|
+
updatedAt: {
|
|
3035
|
+
type: "string",
|
|
3036
|
+
format: "date-time",
|
|
3037
|
+
description: "Timestamp when the team was last updated"
|
|
3038
|
+
}
|
|
3039
|
+
},
|
|
3040
|
+
required: [
|
|
3041
|
+
"id",
|
|
3042
|
+
"name",
|
|
3043
|
+
"organizationId",
|
|
3044
|
+
"createdAt",
|
|
3045
|
+
"updatedAt"
|
|
3046
|
+
]
|
|
3047
|
+
} } }
|
|
3048
|
+
} }
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
}, async (ctx) => {
|
|
3052
|
+
const session = ctx.context.session;
|
|
3053
|
+
const organizationId = ctx.body.data.organizationId || session.session.activeOrganizationId;
|
|
3054
|
+
if (!organizationId) return ctx.json(null, {
|
|
3055
|
+
status: 400,
|
|
3056
|
+
body: { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION }
|
|
3057
|
+
});
|
|
3058
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
3059
|
+
const member = await adapter.findMemberByOrgId({
|
|
3060
|
+
userId: session.user.id,
|
|
3061
|
+
organizationId
|
|
3062
|
+
});
|
|
3063
|
+
if (!member) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM });
|
|
3064
|
+
if (!await hasPermission({
|
|
3065
|
+
role: member.role,
|
|
3066
|
+
options: ctx.context.orgOptions,
|
|
3067
|
+
permissions: { team: ["update"] },
|
|
3068
|
+
organizationId
|
|
3069
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM });
|
|
3070
|
+
const team = await adapter.findTeamById({
|
|
3071
|
+
teamId: ctx.body.teamId,
|
|
3072
|
+
organizationId
|
|
3073
|
+
});
|
|
3074
|
+
if (!team || team.organizationId !== organizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
3075
|
+
const { name, organizationId: __,...additionalFields } = ctx.body.data;
|
|
3076
|
+
const organization$1 = await adapter.findOrganizationById(organizationId);
|
|
3077
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
3078
|
+
const updates = {
|
|
3079
|
+
name,
|
|
3080
|
+
...additionalFields
|
|
3081
|
+
};
|
|
3082
|
+
if (options?.organizationHooks?.beforeUpdateTeam) {
|
|
3083
|
+
const response = await options?.organizationHooks.beforeUpdateTeam({
|
|
3084
|
+
team,
|
|
3085
|
+
updates,
|
|
3086
|
+
user: session.user,
|
|
3087
|
+
organization: organization$1
|
|
3088
|
+
});
|
|
3089
|
+
if (response && typeof response === "object" && "data" in response) {
|
|
3090
|
+
const modifiedUpdates = response.data;
|
|
3091
|
+
const updatedTeam$1 = await adapter.updateTeam(team.id, modifiedUpdates);
|
|
3092
|
+
if (options?.organizationHooks?.afterUpdateTeam) await options?.organizationHooks.afterUpdateTeam({
|
|
3093
|
+
team: updatedTeam$1,
|
|
3094
|
+
user: session.user,
|
|
3095
|
+
organization: organization$1
|
|
3096
|
+
});
|
|
3097
|
+
return ctx.json(updatedTeam$1);
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
const updatedTeam = await adapter.updateTeam(team.id, updates);
|
|
3101
|
+
if (options?.organizationHooks?.afterUpdateTeam) await options?.organizationHooks.afterUpdateTeam({
|
|
3102
|
+
team: updatedTeam,
|
|
3103
|
+
user: session.user,
|
|
3104
|
+
organization: organization$1
|
|
3105
|
+
});
|
|
3106
|
+
return ctx.json(updatedTeam);
|
|
3107
|
+
});
|
|
3108
|
+
};
|
|
3109
|
+
const listOrganizationTeams = (options) => createAuthEndpoint("/organization/list-teams", {
|
|
3110
|
+
method: "GET",
|
|
3111
|
+
query: z$1.optional(z$1.object({ organizationId: z$1.string().meta({ description: `The organization ID which the teams are under to list. Defaults to the users active organization. Eg: "organziation-id"` }).optional() })),
|
|
3112
|
+
requireHeaders: true,
|
|
3113
|
+
metadata: { openapi: {
|
|
3114
|
+
description: "List all teams in an organization",
|
|
3115
|
+
responses: { "200": {
|
|
3116
|
+
description: "Teams retrieved successfully",
|
|
3117
|
+
content: { "application/json": { schema: {
|
|
3118
|
+
type: "array",
|
|
3119
|
+
items: {
|
|
3120
|
+
type: "object",
|
|
3121
|
+
properties: {
|
|
3122
|
+
id: {
|
|
3123
|
+
type: "string",
|
|
3124
|
+
description: "Unique identifier of the team"
|
|
3125
|
+
},
|
|
3126
|
+
name: {
|
|
3127
|
+
type: "string",
|
|
3128
|
+
description: "Name of the team"
|
|
3129
|
+
},
|
|
3130
|
+
organizationId: {
|
|
3131
|
+
type: "string",
|
|
3132
|
+
description: "ID of the organization the team belongs to"
|
|
3133
|
+
},
|
|
3134
|
+
createdAt: {
|
|
3135
|
+
type: "string",
|
|
3136
|
+
format: "date-time",
|
|
3137
|
+
description: "Timestamp when the team was created"
|
|
3138
|
+
},
|
|
3139
|
+
updatedAt: {
|
|
3140
|
+
type: "string",
|
|
3141
|
+
format: "date-time",
|
|
3142
|
+
description: "Timestamp when the team was last updated"
|
|
3143
|
+
}
|
|
3144
|
+
},
|
|
3145
|
+
required: [
|
|
3146
|
+
"id",
|
|
3147
|
+
"name",
|
|
3148
|
+
"organizationId",
|
|
3149
|
+
"createdAt",
|
|
3150
|
+
"updatedAt"
|
|
3151
|
+
]
|
|
3152
|
+
},
|
|
3153
|
+
description: "Array of team objects within the organization"
|
|
3154
|
+
} } }
|
|
3155
|
+
} }
|
|
3156
|
+
} },
|
|
3157
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
3158
|
+
}, async (ctx) => {
|
|
3159
|
+
const session = ctx.context.session;
|
|
3160
|
+
const organizationId = ctx.query?.organizationId || session?.session.activeOrganizationId;
|
|
3161
|
+
if (!organizationId) throw ctx.error("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
3162
|
+
const adapter = getOrgAdapter(ctx.context, options);
|
|
3163
|
+
if (!await adapter.findMemberByOrgId({
|
|
3164
|
+
userId: session.user.id,
|
|
3165
|
+
organizationId: organizationId || ""
|
|
3166
|
+
})) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION });
|
|
3167
|
+
const teams = await adapter.listTeams(organizationId);
|
|
3168
|
+
return ctx.json(teams);
|
|
3169
|
+
});
|
|
3170
|
+
const setActiveTeam = (options) => createAuthEndpoint("/organization/set-active-team", {
|
|
3171
|
+
method: "POST",
|
|
3172
|
+
body: z$1.object({ teamId: z$1.string().meta({ description: "The team id to set as active. It can be null to unset the active team" }).nullable().optional() }),
|
|
3173
|
+
use: [orgSessionMiddleware, orgMiddleware],
|
|
3174
|
+
metadata: { openapi: {
|
|
3175
|
+
description: "Set the active team",
|
|
3176
|
+
responses: { "200": {
|
|
3177
|
+
description: "Success",
|
|
3178
|
+
content: { "application/json": { schema: {
|
|
3179
|
+
type: "object",
|
|
3180
|
+
description: "The team",
|
|
3181
|
+
$ref: "#/components/schemas/Team"
|
|
3182
|
+
} } }
|
|
3183
|
+
} }
|
|
3184
|
+
} }
|
|
3185
|
+
}, async (ctx) => {
|
|
3186
|
+
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
|
3187
|
+
const session = ctx.context.session;
|
|
3188
|
+
if (ctx.body.teamId === null) {
|
|
3189
|
+
if (!session.session.activeTeamId) return ctx.json(null);
|
|
3190
|
+
await setSessionCookie(ctx, {
|
|
3191
|
+
session: await adapter.setActiveTeam(session.session.token, null, ctx),
|
|
3192
|
+
user: session.user
|
|
3193
|
+
});
|
|
3194
|
+
return ctx.json(null);
|
|
3195
|
+
}
|
|
3196
|
+
let teamId;
|
|
3197
|
+
if (!ctx.body.teamId) {
|
|
3198
|
+
const sessionTeamId = session.session.activeTeamId;
|
|
3199
|
+
if (!sessionTeamId) return ctx.json(null);
|
|
3200
|
+
else teamId = sessionTeamId;
|
|
3201
|
+
} else teamId = ctx.body.teamId;
|
|
3202
|
+
const team = await adapter.findTeamById({ teamId });
|
|
3203
|
+
if (!team) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
3204
|
+
if (!await adapter.findTeamMember({
|
|
3205
|
+
teamId,
|
|
3206
|
+
userId: session.user.id
|
|
3207
|
+
})) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_TEAM });
|
|
3208
|
+
await setSessionCookie(ctx, {
|
|
3209
|
+
session: await adapter.setActiveTeam(session.session.token, team.id, ctx),
|
|
3210
|
+
user: session.user
|
|
3211
|
+
});
|
|
3212
|
+
return ctx.json(team);
|
|
3213
|
+
});
|
|
3214
|
+
const listUserTeams = (options) => createAuthEndpoint("/organization/list-user-teams", {
|
|
3215
|
+
method: "GET",
|
|
3216
|
+
metadata: { openapi: {
|
|
3217
|
+
description: "List all teams that the current user is a part of.",
|
|
3218
|
+
responses: { "200": {
|
|
3219
|
+
description: "Teams retrieved successfully",
|
|
3220
|
+
content: { "application/json": { schema: {
|
|
3221
|
+
type: "array",
|
|
3222
|
+
items: {
|
|
3223
|
+
type: "object",
|
|
3224
|
+
description: "The team",
|
|
3225
|
+
$ref: "#/components/schemas/Team"
|
|
3226
|
+
},
|
|
3227
|
+
description: "Array of team objects within the organization"
|
|
3228
|
+
} } }
|
|
3229
|
+
} }
|
|
3230
|
+
} },
|
|
3231
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
3232
|
+
}, async (ctx) => {
|
|
3233
|
+
const session = ctx.context.session;
|
|
3234
|
+
const teams = await getOrgAdapter(ctx.context, ctx.context.orgOptions).listTeamsByUser({ userId: session.user.id });
|
|
3235
|
+
return ctx.json(teams);
|
|
3236
|
+
});
|
|
3237
|
+
const listTeamMembers = (options) => createAuthEndpoint("/organization/list-team-members", {
|
|
3238
|
+
method: "GET",
|
|
3239
|
+
query: z$1.optional(z$1.object({ teamId: z$1.string().optional().meta({ description: "The team whose members we should return. If this is not provided the members of the current active team get returned." }) })),
|
|
3240
|
+
metadata: { openapi: {
|
|
3241
|
+
description: "List the members of the given team.",
|
|
3242
|
+
responses: { "200": {
|
|
3243
|
+
description: "Teams retrieved successfully",
|
|
3244
|
+
content: { "application/json": { schema: {
|
|
3245
|
+
type: "array",
|
|
3246
|
+
items: {
|
|
3247
|
+
type: "object",
|
|
3248
|
+
description: "The team member",
|
|
3249
|
+
properties: {
|
|
3250
|
+
id: {
|
|
3251
|
+
type: "string",
|
|
3252
|
+
description: "Unique identifier of the team member"
|
|
3253
|
+
},
|
|
3254
|
+
userId: {
|
|
3255
|
+
type: "string",
|
|
3256
|
+
description: "The user ID of the team member"
|
|
3257
|
+
},
|
|
3258
|
+
teamId: {
|
|
3259
|
+
type: "string",
|
|
3260
|
+
description: "The team ID of the team the team member is in"
|
|
3261
|
+
},
|
|
3262
|
+
createdAt: {
|
|
3263
|
+
type: "string",
|
|
3264
|
+
format: "date-time",
|
|
3265
|
+
description: "Timestamp when the team member was created"
|
|
3266
|
+
}
|
|
3267
|
+
},
|
|
3268
|
+
required: [
|
|
3269
|
+
"id",
|
|
3270
|
+
"userId",
|
|
3271
|
+
"teamId",
|
|
3272
|
+
"createdAt"
|
|
3273
|
+
]
|
|
3274
|
+
},
|
|
3275
|
+
description: "Array of team member objects within the team"
|
|
3276
|
+
} } }
|
|
3277
|
+
} }
|
|
3278
|
+
} },
|
|
3279
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
3280
|
+
}, async (ctx) => {
|
|
3281
|
+
const session = ctx.context.session;
|
|
3282
|
+
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
|
3283
|
+
let teamId = ctx.query?.teamId || session?.session.activeTeamId;
|
|
3284
|
+
if (!teamId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM });
|
|
3285
|
+
if (!await adapter.findTeamMember({
|
|
3286
|
+
userId: session.user.id,
|
|
3287
|
+
teamId
|
|
3288
|
+
})) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_TEAM });
|
|
3289
|
+
const members = await adapter.listTeamMembers({ teamId });
|
|
3290
|
+
return ctx.json(members);
|
|
3291
|
+
});
|
|
3292
|
+
const addTeamMember = (options) => createAuthEndpoint("/organization/add-team-member", {
|
|
3293
|
+
method: "POST",
|
|
3294
|
+
body: z$1.object({
|
|
3295
|
+
teamId: z$1.string().meta({ description: "The team the user should be a member of." }),
|
|
3296
|
+
userId: z$1.coerce.string().meta({ description: "The user Id which represents the user to be added as a member." })
|
|
3297
|
+
}),
|
|
3298
|
+
metadata: { openapi: {
|
|
3299
|
+
description: "The newly created member",
|
|
3300
|
+
responses: { "200": {
|
|
3301
|
+
description: "Team member created successfully",
|
|
3302
|
+
content: { "application/json": { schema: {
|
|
3303
|
+
type: "object",
|
|
3304
|
+
description: "The team member",
|
|
3305
|
+
properties: {
|
|
3306
|
+
id: {
|
|
3307
|
+
type: "string",
|
|
3308
|
+
description: "Unique identifier of the team member"
|
|
3309
|
+
},
|
|
3310
|
+
userId: {
|
|
3311
|
+
type: "string",
|
|
3312
|
+
description: "The user ID of the team member"
|
|
3313
|
+
},
|
|
3314
|
+
teamId: {
|
|
3315
|
+
type: "string",
|
|
3316
|
+
description: "The team ID of the team the team member is in"
|
|
3317
|
+
},
|
|
3318
|
+
createdAt: {
|
|
3319
|
+
type: "string",
|
|
3320
|
+
format: "date-time",
|
|
3321
|
+
description: "Timestamp when the team member was created"
|
|
3322
|
+
}
|
|
3323
|
+
},
|
|
3324
|
+
required: [
|
|
3325
|
+
"id",
|
|
3326
|
+
"userId",
|
|
3327
|
+
"teamId",
|
|
3328
|
+
"createdAt"
|
|
3329
|
+
]
|
|
3330
|
+
} } }
|
|
3331
|
+
} }
|
|
3332
|
+
} },
|
|
3333
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
3334
|
+
}, async (ctx) => {
|
|
3335
|
+
const session = ctx.context.session;
|
|
3336
|
+
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
|
3337
|
+
if (!session.session.activeOrganizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
3338
|
+
const currentMember = await adapter.findMemberByOrgId({
|
|
3339
|
+
userId: session.user.id,
|
|
3340
|
+
organizationId: session.session.activeOrganizationId
|
|
3341
|
+
});
|
|
3342
|
+
if (!currentMember) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
3343
|
+
if (!await hasPermission({
|
|
3344
|
+
role: currentMember.role,
|
|
3345
|
+
options: ctx.context.orgOptions,
|
|
3346
|
+
permissions: { member: ["update"] },
|
|
3347
|
+
organizationId: session.session.activeOrganizationId
|
|
3348
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER });
|
|
3349
|
+
if (!await adapter.findMemberByOrgId({
|
|
3350
|
+
userId: ctx.body.userId,
|
|
3351
|
+
organizationId: session.session.activeOrganizationId
|
|
3352
|
+
})) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
3353
|
+
const team = await adapter.findTeamById({
|
|
3354
|
+
teamId: ctx.body.teamId,
|
|
3355
|
+
organizationId: session.session.activeOrganizationId
|
|
3356
|
+
});
|
|
3357
|
+
if (!team) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
3358
|
+
const organization$1 = await adapter.findOrganizationById(session.session.activeOrganizationId);
|
|
3359
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
3360
|
+
const userBeingAdded = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
|
|
3361
|
+
if (!userBeingAdded) throw new APIError("BAD_REQUEST", { message: "User not found" });
|
|
3362
|
+
if (options?.organizationHooks?.beforeAddTeamMember) {
|
|
3363
|
+
const response = await options?.organizationHooks.beforeAddTeamMember({
|
|
3364
|
+
teamMember: {
|
|
3365
|
+
teamId: ctx.body.teamId,
|
|
3366
|
+
userId: ctx.body.userId
|
|
3367
|
+
},
|
|
3368
|
+
team,
|
|
3369
|
+
user: userBeingAdded,
|
|
3370
|
+
organization: organization$1
|
|
3371
|
+
});
|
|
3372
|
+
if (response && typeof response === "object" && "data" in response) {}
|
|
3373
|
+
}
|
|
3374
|
+
const teamMember = await adapter.findOrCreateTeamMember({
|
|
3375
|
+
teamId: ctx.body.teamId,
|
|
3376
|
+
userId: ctx.body.userId
|
|
3377
|
+
});
|
|
3378
|
+
if (options?.organizationHooks?.afterAddTeamMember) await options?.organizationHooks.afterAddTeamMember({
|
|
3379
|
+
teamMember,
|
|
3380
|
+
team,
|
|
3381
|
+
user: userBeingAdded,
|
|
3382
|
+
organization: organization$1
|
|
3383
|
+
});
|
|
3384
|
+
return ctx.json(teamMember);
|
|
3385
|
+
});
|
|
3386
|
+
const removeTeamMember = (options) => createAuthEndpoint("/organization/remove-team-member", {
|
|
3387
|
+
method: "POST",
|
|
3388
|
+
body: z$1.object({
|
|
3389
|
+
teamId: z$1.string().meta({ description: "The team the user should be removed from." }),
|
|
3390
|
+
userId: z$1.coerce.string().meta({ description: "The user which should be removed from the team." })
|
|
3391
|
+
}),
|
|
3392
|
+
metadata: { openapi: {
|
|
3393
|
+
description: "Remove a member from a team",
|
|
3394
|
+
responses: { "200": {
|
|
3395
|
+
description: "Team member removed successfully",
|
|
3396
|
+
content: { "application/json": { schema: {
|
|
3397
|
+
type: "object",
|
|
3398
|
+
properties: { message: {
|
|
3399
|
+
type: "string",
|
|
3400
|
+
description: "Confirmation message indicating successful removal",
|
|
3401
|
+
enum: ["Team member removed successfully."]
|
|
3402
|
+
} },
|
|
3403
|
+
required: ["message"]
|
|
3404
|
+
} } }
|
|
3405
|
+
} }
|
|
3406
|
+
} },
|
|
3407
|
+
use: [orgMiddleware, orgSessionMiddleware]
|
|
3408
|
+
}, async (ctx) => {
|
|
3409
|
+
const session = ctx.context.session;
|
|
3410
|
+
const adapter = getOrgAdapter(ctx.context, ctx.context.orgOptions);
|
|
3411
|
+
if (!session.session.activeOrganizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
3412
|
+
const currentMember = await adapter.findMemberByOrgId({
|
|
3413
|
+
userId: session.user.id,
|
|
3414
|
+
organizationId: session.session.activeOrganizationId
|
|
3415
|
+
});
|
|
3416
|
+
if (!currentMember) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
3417
|
+
if (!await hasPermission({
|
|
3418
|
+
role: currentMember.role,
|
|
3419
|
+
options: ctx.context.orgOptions,
|
|
3420
|
+
permissions: { member: ["delete"] },
|
|
3421
|
+
organizationId: session.session.activeOrganizationId
|
|
3422
|
+
}, ctx)) throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER });
|
|
3423
|
+
if (!await adapter.findMemberByOrgId({
|
|
3424
|
+
userId: ctx.body.userId,
|
|
3425
|
+
organizationId: session.session.activeOrganizationId
|
|
3426
|
+
})) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
3427
|
+
const team = await adapter.findTeamById({
|
|
3428
|
+
teamId: ctx.body.teamId,
|
|
3429
|
+
organizationId: session.session.activeOrganizationId
|
|
3430
|
+
});
|
|
3431
|
+
if (!team) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.TEAM_NOT_FOUND });
|
|
3432
|
+
const organization$1 = await adapter.findOrganizationById(session.session.activeOrganizationId);
|
|
3433
|
+
if (!organization$1) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND });
|
|
3434
|
+
const userBeingRemoved = await ctx.context.internalAdapter.findUserById(ctx.body.userId);
|
|
3435
|
+
if (!userBeingRemoved) throw new APIError("BAD_REQUEST", { message: "User not found" });
|
|
3436
|
+
const teamMember = await adapter.findTeamMember({
|
|
3437
|
+
teamId: ctx.body.teamId,
|
|
3438
|
+
userId: ctx.body.userId
|
|
3439
|
+
});
|
|
3440
|
+
if (!teamMember) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_TEAM });
|
|
3441
|
+
if (options?.organizationHooks?.beforeRemoveTeamMember) await options?.organizationHooks.beforeRemoveTeamMember({
|
|
3442
|
+
teamMember,
|
|
3443
|
+
team,
|
|
3444
|
+
user: userBeingRemoved,
|
|
3445
|
+
organization: organization$1
|
|
3446
|
+
});
|
|
3447
|
+
await adapter.removeTeamMember({
|
|
3448
|
+
teamId: ctx.body.teamId,
|
|
3449
|
+
userId: ctx.body.userId
|
|
3450
|
+
});
|
|
3451
|
+
if (options?.organizationHooks?.afterRemoveTeamMember) await options?.organizationHooks.afterRemoveTeamMember({
|
|
3452
|
+
teamMember,
|
|
3453
|
+
team,
|
|
3454
|
+
user: userBeingRemoved,
|
|
3455
|
+
organization: organization$1
|
|
3456
|
+
});
|
|
3457
|
+
return ctx.json({ message: "Team member removed successfully." });
|
|
3458
|
+
});
|
|
3459
|
+
|
|
3460
|
+
//#endregion
|
|
3461
|
+
//#region src/plugins/organization/organization.ts
|
|
3462
|
+
function parseRoles(roles) {
|
|
3463
|
+
return Array.isArray(roles) ? roles.join(",") : roles;
|
|
3464
|
+
}
|
|
3465
|
+
const createHasPermission = (options) => {
|
|
3466
|
+
return createAuthEndpoint("/organization/has-permission", {
|
|
3467
|
+
method: "POST",
|
|
3468
|
+
requireHeaders: true,
|
|
3469
|
+
body: z$1.object({ organizationId: z$1.string().optional() }).and(z$1.union([z$1.object({
|
|
3470
|
+
permission: z$1.record(z$1.string(), z$1.array(z$1.string())),
|
|
3471
|
+
permissions: z$1.undefined()
|
|
3472
|
+
}), z$1.object({
|
|
3473
|
+
permission: z$1.undefined(),
|
|
3474
|
+
permissions: z$1.record(z$1.string(), z$1.array(z$1.string()))
|
|
3475
|
+
})])),
|
|
3476
|
+
use: [orgSessionMiddleware],
|
|
3477
|
+
metadata: {
|
|
3478
|
+
$Infer: { body: {} },
|
|
3479
|
+
openapi: {
|
|
3480
|
+
description: "Check if the user has permission",
|
|
3481
|
+
requestBody: { content: { "application/json": { schema: {
|
|
3482
|
+
type: "object",
|
|
3483
|
+
properties: {
|
|
3484
|
+
permission: {
|
|
3485
|
+
type: "object",
|
|
3486
|
+
description: "The permission to check",
|
|
3487
|
+
deprecated: true
|
|
3488
|
+
},
|
|
3489
|
+
permissions: {
|
|
3490
|
+
type: "object",
|
|
3491
|
+
description: "The permission to check"
|
|
3492
|
+
}
|
|
3493
|
+
},
|
|
3494
|
+
required: ["permissions"]
|
|
3495
|
+
} } } },
|
|
3496
|
+
responses: { "200": {
|
|
3497
|
+
description: "Success",
|
|
3498
|
+
content: { "application/json": { schema: {
|
|
3499
|
+
type: "object",
|
|
3500
|
+
properties: {
|
|
3501
|
+
error: { type: "string" },
|
|
3502
|
+
success: { type: "boolean" }
|
|
3503
|
+
},
|
|
3504
|
+
required: ["success"]
|
|
3505
|
+
} } }
|
|
3506
|
+
} }
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}, async (ctx) => {
|
|
3510
|
+
const activeOrganizationId = ctx.body.organizationId || ctx.context.session.session.activeOrganizationId;
|
|
3511
|
+
if (!activeOrganizationId) throw new APIError("BAD_REQUEST", { message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION });
|
|
3512
|
+
const member = await getOrgAdapter(ctx.context, options).findMemberByOrgId({
|
|
3513
|
+
userId: ctx.context.session.user.id,
|
|
3514
|
+
organizationId: activeOrganizationId
|
|
3515
|
+
});
|
|
3516
|
+
if (!member) throw new APIError("UNAUTHORIZED", { message: ORGANIZATION_ERROR_CODES.USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION });
|
|
3517
|
+
const result = await hasPermission({
|
|
3518
|
+
role: member.role,
|
|
3519
|
+
options: options || {},
|
|
3520
|
+
permissions: ctx.body.permissions ?? ctx.body.permission,
|
|
3521
|
+
organizationId: activeOrganizationId
|
|
3522
|
+
}, ctx);
|
|
3523
|
+
return ctx.json({
|
|
3524
|
+
error: null,
|
|
3525
|
+
success: result
|
|
3526
|
+
});
|
|
3527
|
+
});
|
|
3528
|
+
};
|
|
3529
|
+
function organization(options) {
|
|
3530
|
+
let endpoints = {
|
|
3531
|
+
createOrganization: createOrganization(options),
|
|
3532
|
+
updateOrganization: updateOrganization(options),
|
|
3533
|
+
deleteOrganization: deleteOrganization(options),
|
|
3534
|
+
setActiveOrganization: setActiveOrganization(options),
|
|
3535
|
+
getFullOrganization: getFullOrganization(options),
|
|
3536
|
+
listOrganizations: listOrganizations(options),
|
|
3537
|
+
createInvitation: createInvitation(options),
|
|
3538
|
+
cancelInvitation: cancelInvitation(options),
|
|
3539
|
+
acceptInvitation: acceptInvitation(options),
|
|
3540
|
+
getInvitation: getInvitation(options),
|
|
3541
|
+
rejectInvitation: rejectInvitation(options),
|
|
3542
|
+
listInvitations: listInvitations(options),
|
|
3543
|
+
getActiveMember: getActiveMember(options),
|
|
3544
|
+
checkOrganizationSlug: checkOrganizationSlug(options),
|
|
3545
|
+
addMember: addMember(options),
|
|
3546
|
+
removeMember: removeMember(options),
|
|
3547
|
+
updateMemberRole: updateMemberRole(options),
|
|
3548
|
+
leaveOrganization: leaveOrganization(options),
|
|
3549
|
+
listUserInvitations: listUserInvitations(options),
|
|
3550
|
+
listMembers: listMembers(options),
|
|
3551
|
+
getActiveMemberRole: getActiveMemberRole(options)
|
|
3552
|
+
};
|
|
3553
|
+
const teamSupport = options?.teams?.enabled;
|
|
3554
|
+
const teamEndpoints = {
|
|
3555
|
+
createTeam: createTeam(options),
|
|
3556
|
+
listOrganizationTeams: listOrganizationTeams(options),
|
|
3557
|
+
removeTeam: removeTeam(options),
|
|
3558
|
+
updateTeam: updateTeam(options),
|
|
3559
|
+
setActiveTeam: setActiveTeam(options),
|
|
3560
|
+
listUserTeams: listUserTeams(options),
|
|
3561
|
+
listTeamMembers: listTeamMembers(options),
|
|
3562
|
+
addTeamMember: addTeamMember(options),
|
|
3563
|
+
removeTeamMember: removeTeamMember(options)
|
|
3564
|
+
};
|
|
3565
|
+
if (teamSupport) endpoints = {
|
|
3566
|
+
...endpoints,
|
|
3567
|
+
...teamEndpoints
|
|
3568
|
+
};
|
|
3569
|
+
const dynamicAccessControlEndpoints = {
|
|
3570
|
+
createOrgRole: createOrgRole(options),
|
|
3571
|
+
deleteOrgRole: deleteOrgRole(options),
|
|
3572
|
+
listOrgRoles: listOrgRoles(options),
|
|
3573
|
+
getOrgRole: getOrgRole(options),
|
|
3574
|
+
updateOrgRole: updateOrgRole(options)
|
|
3575
|
+
};
|
|
3576
|
+
if (options?.dynamicAccessControl?.enabled) endpoints = {
|
|
3577
|
+
...endpoints,
|
|
3578
|
+
...dynamicAccessControlEndpoints
|
|
3579
|
+
};
|
|
3580
|
+
const roles = {
|
|
3581
|
+
...defaultRoles,
|
|
3582
|
+
...options?.roles
|
|
3583
|
+
};
|
|
3584
|
+
const teamSchema$1 = teamSupport ? {
|
|
3585
|
+
team: {
|
|
3586
|
+
modelName: options?.schema?.team?.modelName,
|
|
3587
|
+
fields: {
|
|
3588
|
+
name: {
|
|
3589
|
+
type: "string",
|
|
3590
|
+
required: true,
|
|
3591
|
+
fieldName: options?.schema?.team?.fields?.name
|
|
3592
|
+
},
|
|
3593
|
+
organizationId: {
|
|
3594
|
+
type: "string",
|
|
3595
|
+
required: true,
|
|
3596
|
+
references: {
|
|
3597
|
+
model: "organization",
|
|
3598
|
+
field: "id"
|
|
3599
|
+
},
|
|
3600
|
+
fieldName: options?.schema?.team?.fields?.organizationId
|
|
3601
|
+
},
|
|
3602
|
+
createdAt: {
|
|
3603
|
+
type: "date",
|
|
3604
|
+
required: true,
|
|
3605
|
+
fieldName: options?.schema?.team?.fields?.createdAt
|
|
3606
|
+
},
|
|
3607
|
+
updatedAt: {
|
|
3608
|
+
type: "date",
|
|
3609
|
+
required: false,
|
|
3610
|
+
fieldName: options?.schema?.team?.fields?.updatedAt,
|
|
3611
|
+
onUpdate: () => /* @__PURE__ */ new Date()
|
|
3612
|
+
},
|
|
3613
|
+
...options?.schema?.team?.additionalFields || {}
|
|
3614
|
+
}
|
|
3615
|
+
},
|
|
3616
|
+
teamMember: {
|
|
3617
|
+
modelName: options?.schema?.teamMember?.modelName,
|
|
3618
|
+
fields: {
|
|
3619
|
+
teamId: {
|
|
3620
|
+
type: "string",
|
|
3621
|
+
required: true,
|
|
3622
|
+
references: {
|
|
3623
|
+
model: "team",
|
|
3624
|
+
field: "id"
|
|
3625
|
+
},
|
|
3626
|
+
fieldName: options?.schema?.teamMember?.fields?.teamId
|
|
3627
|
+
},
|
|
3628
|
+
userId: {
|
|
3629
|
+
type: "string",
|
|
3630
|
+
required: true,
|
|
3631
|
+
references: {
|
|
3632
|
+
model: "user",
|
|
3633
|
+
field: "id"
|
|
3634
|
+
},
|
|
3635
|
+
fieldName: options?.schema?.teamMember?.fields?.userId
|
|
3636
|
+
},
|
|
3637
|
+
createdAt: {
|
|
3638
|
+
type: "date",
|
|
3639
|
+
required: false,
|
|
3640
|
+
fieldName: options?.schema?.teamMember?.fields?.createdAt
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
} : {};
|
|
3645
|
+
const organizationRoleSchema$1 = options?.dynamicAccessControl?.enabled ? { organizationRole: {
|
|
3646
|
+
fields: {
|
|
3647
|
+
organizationId: {
|
|
3648
|
+
type: "string",
|
|
3649
|
+
required: true,
|
|
3650
|
+
references: {
|
|
3651
|
+
model: "organization",
|
|
3652
|
+
field: "id"
|
|
3653
|
+
},
|
|
3654
|
+
fieldName: options?.schema?.organizationRole?.fields?.organizationId
|
|
3655
|
+
},
|
|
3656
|
+
role: {
|
|
3657
|
+
type: "string",
|
|
3658
|
+
required: true,
|
|
3659
|
+
fieldName: options?.schema?.organizationRole?.fields?.role
|
|
3660
|
+
},
|
|
3661
|
+
permission: {
|
|
3662
|
+
type: "string",
|
|
3663
|
+
required: true,
|
|
3664
|
+
fieldName: options?.schema?.organizationRole?.fields?.permission
|
|
3665
|
+
},
|
|
3666
|
+
createdAt: {
|
|
3667
|
+
type: "date",
|
|
3668
|
+
required: true,
|
|
3669
|
+
defaultValue: () => /* @__PURE__ */ new Date(),
|
|
3670
|
+
fieldName: options?.schema?.organizationRole?.fields?.createdAt
|
|
3671
|
+
},
|
|
3672
|
+
updatedAt: {
|
|
3673
|
+
type: "date",
|
|
3674
|
+
required: false,
|
|
3675
|
+
fieldName: options?.schema?.organizationRole?.fields?.updatedAt,
|
|
3676
|
+
onUpdate: () => /* @__PURE__ */ new Date()
|
|
3677
|
+
},
|
|
3678
|
+
...options?.schema?.organizationRole?.additionalFields || {}
|
|
3679
|
+
},
|
|
3680
|
+
modelName: options?.schema?.organizationRole?.modelName
|
|
3681
|
+
} } : {};
|
|
3682
|
+
const schema = {
|
|
3683
|
+
organization: {
|
|
3684
|
+
modelName: options?.schema?.organization?.modelName,
|
|
3685
|
+
fields: {
|
|
3686
|
+
name: {
|
|
3687
|
+
type: "string",
|
|
3688
|
+
required: true,
|
|
3689
|
+
sortable: true,
|
|
3690
|
+
fieldName: options?.schema?.organization?.fields?.name
|
|
3691
|
+
},
|
|
3692
|
+
slug: {
|
|
3693
|
+
type: "string",
|
|
3694
|
+
required: true,
|
|
3695
|
+
unique: true,
|
|
3696
|
+
sortable: true,
|
|
3697
|
+
fieldName: options?.schema?.organization?.fields?.slug
|
|
3698
|
+
},
|
|
3699
|
+
logo: {
|
|
3700
|
+
type: "string",
|
|
3701
|
+
required: false,
|
|
3702
|
+
fieldName: options?.schema?.organization?.fields?.logo
|
|
3703
|
+
},
|
|
3704
|
+
createdAt: {
|
|
3705
|
+
type: "date",
|
|
3706
|
+
required: true,
|
|
3707
|
+
fieldName: options?.schema?.organization?.fields?.createdAt
|
|
3708
|
+
},
|
|
3709
|
+
metadata: {
|
|
3710
|
+
type: "string",
|
|
3711
|
+
required: false,
|
|
3712
|
+
fieldName: options?.schema?.organization?.fields?.metadata
|
|
3713
|
+
},
|
|
3714
|
+
...options?.schema?.organization?.additionalFields || {}
|
|
3715
|
+
}
|
|
3716
|
+
},
|
|
3717
|
+
...organizationRoleSchema$1,
|
|
3718
|
+
...teamSchema$1,
|
|
3719
|
+
member: {
|
|
3720
|
+
modelName: options?.schema?.member?.modelName,
|
|
3721
|
+
fields: {
|
|
3722
|
+
organizationId: {
|
|
3723
|
+
type: "string",
|
|
3724
|
+
required: true,
|
|
3725
|
+
references: {
|
|
3726
|
+
model: "organization",
|
|
3727
|
+
field: "id"
|
|
3728
|
+
},
|
|
3729
|
+
fieldName: options?.schema?.member?.fields?.organizationId
|
|
3730
|
+
},
|
|
3731
|
+
userId: {
|
|
3732
|
+
type: "string",
|
|
3733
|
+
required: true,
|
|
3734
|
+
fieldName: options?.schema?.member?.fields?.userId,
|
|
3735
|
+
references: {
|
|
3736
|
+
model: "user",
|
|
3737
|
+
field: "id"
|
|
3738
|
+
}
|
|
3739
|
+
},
|
|
3740
|
+
role: {
|
|
3741
|
+
type: "string",
|
|
3742
|
+
required: true,
|
|
3743
|
+
sortable: true,
|
|
3744
|
+
defaultValue: "member",
|
|
3745
|
+
fieldName: options?.schema?.member?.fields?.role
|
|
3746
|
+
},
|
|
3747
|
+
createdAt: {
|
|
3748
|
+
type: "date",
|
|
3749
|
+
required: true,
|
|
3750
|
+
fieldName: options?.schema?.member?.fields?.createdAt
|
|
3751
|
+
},
|
|
3752
|
+
...options?.schema?.member?.additionalFields || {}
|
|
3753
|
+
}
|
|
3754
|
+
},
|
|
3755
|
+
invitation: {
|
|
3756
|
+
modelName: options?.schema?.invitation?.modelName,
|
|
3757
|
+
fields: {
|
|
3758
|
+
organizationId: {
|
|
3759
|
+
type: "string",
|
|
3760
|
+
required: true,
|
|
3761
|
+
references: {
|
|
3762
|
+
model: "organization",
|
|
3763
|
+
field: "id"
|
|
3764
|
+
},
|
|
3765
|
+
fieldName: options?.schema?.invitation?.fields?.organizationId
|
|
3766
|
+
},
|
|
3767
|
+
email: {
|
|
3768
|
+
type: "string",
|
|
3769
|
+
required: true,
|
|
3770
|
+
sortable: true,
|
|
3771
|
+
fieldName: options?.schema?.invitation?.fields?.email
|
|
3772
|
+
},
|
|
3773
|
+
role: {
|
|
3774
|
+
type: "string",
|
|
3775
|
+
required: false,
|
|
3776
|
+
sortable: true,
|
|
3777
|
+
fieldName: options?.schema?.invitation?.fields?.role
|
|
3778
|
+
},
|
|
3779
|
+
...teamSupport ? { teamId: {
|
|
3780
|
+
type: "string",
|
|
3781
|
+
required: false,
|
|
3782
|
+
sortable: true,
|
|
3783
|
+
fieldName: options?.schema?.invitation?.fields?.teamId
|
|
3784
|
+
} } : {},
|
|
3785
|
+
status: {
|
|
3786
|
+
type: "string",
|
|
3787
|
+
required: true,
|
|
3788
|
+
sortable: true,
|
|
3789
|
+
defaultValue: "pending",
|
|
3790
|
+
fieldName: options?.schema?.invitation?.fields?.status
|
|
3791
|
+
},
|
|
3792
|
+
expiresAt: {
|
|
3793
|
+
type: "date",
|
|
3794
|
+
required: true,
|
|
3795
|
+
fieldName: options?.schema?.invitation?.fields?.expiresAt
|
|
3796
|
+
},
|
|
3797
|
+
createdAt: {
|
|
3798
|
+
type: "date",
|
|
3799
|
+
required: true,
|
|
3800
|
+
fieldName: options?.schema?.invitation?.fields?.createdAt,
|
|
3801
|
+
defaultValue: () => /* @__PURE__ */ new Date()
|
|
3802
|
+
},
|
|
3803
|
+
inviterId: {
|
|
3804
|
+
type: "string",
|
|
3805
|
+
references: {
|
|
3806
|
+
model: "user",
|
|
3807
|
+
field: "id"
|
|
3808
|
+
},
|
|
3809
|
+
fieldName: options?.schema?.invitation?.fields?.inviterId,
|
|
3810
|
+
required: true
|
|
3811
|
+
},
|
|
3812
|
+
...options?.schema?.invitation?.additionalFields || {}
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
return {
|
|
3817
|
+
id: "organization",
|
|
3818
|
+
endpoints: {
|
|
3819
|
+
...shimContext(endpoints, {
|
|
3820
|
+
orgOptions: options || {},
|
|
3821
|
+
roles,
|
|
3822
|
+
getSession: async (context) => {
|
|
3823
|
+
return await getSessionFromCtx(context);
|
|
3824
|
+
}
|
|
3825
|
+
}),
|
|
3826
|
+
hasPermission: createHasPermission(options)
|
|
3827
|
+
},
|
|
3828
|
+
schema: {
|
|
3829
|
+
...schema,
|
|
3830
|
+
session: { fields: {
|
|
3831
|
+
activeOrganizationId: {
|
|
3832
|
+
type: "string",
|
|
3833
|
+
required: false,
|
|
3834
|
+
fieldName: options?.schema?.session?.fields?.activeOrganizationId
|
|
3835
|
+
},
|
|
3836
|
+
...teamSupport ? { activeTeamId: {
|
|
3837
|
+
type: "string",
|
|
3838
|
+
required: false,
|
|
3839
|
+
fieldName: options?.schema?.session?.fields?.activeTeamId
|
|
3840
|
+
} } : {}
|
|
3841
|
+
} }
|
|
3842
|
+
},
|
|
3843
|
+
$Infer: {
|
|
3844
|
+
Organization: {},
|
|
3845
|
+
Invitation: {},
|
|
3846
|
+
Member: {},
|
|
3847
|
+
Team: teamSupport ? {} : {},
|
|
3848
|
+
TeamMember: teamSupport ? {} : {},
|
|
3849
|
+
ActiveOrganization: {}
|
|
3850
|
+
},
|
|
3851
|
+
$ERROR_CODES: ORGANIZATION_ERROR_CODES,
|
|
3852
|
+
options
|
|
3853
|
+
};
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
//#endregion
|
|
3857
|
+
export { parseRoles as n, organization as t };
|