1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-21 07:53:34 +00:00

Compare commits

..

432 Commits

Author SHA1 Message Date
3560346f85 update step name 2024-03-22 12:42:32 -04:00
f0bf2f8dd0 seperate aws rds uri 2024-03-22 12:38:10 -04:00
2a6216b8fc deploy to ecs using OIDC with aws 2024-03-22 12:29:07 -04:00
7b84ae6173 Update Chart.yaml 2024-03-21 15:08:38 -04:00
5710a304f8 Merge pull request from Infisical/daniel/k8-operator-machine-identities
Feat: K8 Operator Machine Identity Support
2024-03-21 15:01:50 -04:00
91e3bbba34 Fix: Requested changes 2024-03-21 19:58:10 +01:00
02112ede07 Fix: Requested changes 2024-03-21 19:53:21 +01:00
08cfbf64e4 Fix: Error handing 2024-03-21 19:37:12 +01:00
18da522b45 Chore: Helm charts 2024-03-21 18:35:00 +01:00
8cf68fbd9c Generated 2024-03-21 17:12:42 +01:00
d6b82dfaa4 Fix: Rebase sample conflicts 2024-03-21 17:09:41 +01:00
7bd4eed328 Chore: Generate K8 helm charts 2024-03-21 17:08:01 +01:00
0341c32da0 Fix: Change credentials -> credentialsRef 2024-03-21 17:08:01 +01:00
caea055281 Feat: Improve K8 docs 2024-03-21 17:08:01 +01:00
c08c78de8d Feat: Rename universalAuthMachineIdentity to universalAuth 2024-03-21 17:08:01 +01:00
3765a14246 Fix: Generate new types 2024-03-21 17:08:01 +01:00
c5a11e839b Feat: Deprecate Service Accounts 2024-03-21 17:08:01 +01:00
93bd3d8270 Docs: Simplified docs more 2024-03-21 17:07:56 +01:00
b9601dd418 Update kubernetes.mdx 2024-03-21 17:07:56 +01:00
ae3bc04b07 Docs 2024-03-21 17:07:31 +01:00
11edefa66f Feat: Added project slug support 2024-03-21 17:07:31 +01:00
f71459ede0 Slugs 2024-03-21 17:07:31 +01:00
33324a5a3c Type generation 2024-03-21 17:07:31 +01:00
5c6781a705 Update machine-identity-token.go 2024-03-21 17:07:31 +01:00
71e31518d7 Feat: Add machine identity token handler 2024-03-21 17:07:31 +01:00
f6f6db2898 Fix: Moved update attributes type to models 2024-03-21 17:07:31 +01:00
55780b65d3 Feat: Machine Identity support (token refreshing logic) 2024-03-21 17:07:31 +01:00
83bbf9599d Feat: Machine Identity support 2024-03-21 17:07:31 +01:00
f8f2b2574d Feat: Machine Identity support (types) 2024-03-21 17:07:31 +01:00
318d12addd Feat: Machine Identity support 2024-03-21 17:07:31 +01:00
872a28d02a Feat: Machine Identity support for K8 2024-03-21 17:06:49 +01:00
6f53a5631c Fix: Double prints 2024-03-21 17:06:49 +01:00
ff2098408d Update sample.yaml 2024-03-21 17:06:48 +01:00
9e85d9bbf0 Example 2024-03-21 17:05:46 +01:00
0f3a48bb32 Generated 2024-03-21 17:05:46 +01:00
f869def8ea Added new types 2024-03-21 17:05:46 +01:00
378bc57a88 Merge pull request from akhilmhdh/docs/rotation-doc-update
docs: improved secret rotation documentation with better understanding
2024-03-21 11:17:19 -04:00
242179598b fix types, rephrase, and revise rotation docs 2024-03-21 11:03:41 -04:00
e3e049b66c Update build-staging-and-deploy.yml 2024-03-20 22:14:46 -04:00
878e4a79e7 Merge pull request from Infisical/daniel/ui-imported-folders-fix
Fix: UI indicator for imports
2024-03-20 22:08:50 -04:00
609ce8e5cc Fix: Improved UI import indicators 2024-03-21 03:06:14 +01:00
04c1ea9b11 Update build-staging-and-deploy.yml 2024-03-20 18:03:49 -04:00
3baca73e53 add seperate step for ecr build 2024-03-20 16:25:54 -04:00
36adf6863b Fix: UI secret import indicator 2024-03-20 21:09:54 +01:00
6363e7d30a Update index.ts 2024-03-20 20:26:28 +01:00
f9621fad8e Fix: Remove duplicate type 2024-03-20 20:26:00 +01:00
90be28b87a Feat: Import indicator 2024-03-20 20:25:48 +01:00
671adee4d7 Feat: Indicator for wether or not secrets are imported 2024-03-20 20:24:06 +01:00
c9cb90c98e Feat: Add center property to tooltip 2024-03-20 20:23:21 +01:00
9f691df395 Merge pull request from Infisical/fix-secrets-by-name
set imports=true for get secret by name
2024-03-20 23:50:15 +05:30
d702a61586 set imports=true for get secret by name 2024-03-20 14:06:16 -04:00
1c16f406a7 remove debug 2024-03-20 13:06:29 -04:00
90f739caa6 correct repo name env 2024-03-20 13:05:59 -04:00
ede8b6f286 add .env context for ecr tag 2024-03-20 13:00:06 -04:00
232c547d75 correct ecr image tag 2024-03-20 11:54:33 -04:00
fe08bbb691 push to ecr 2024-03-20 11:46:47 -04:00
2bd06ecde4 login into ecr 2024-03-20 11:31:39 -04:00
08b79d65ea Fix: Remove unused lint disable 2024-03-20 14:55:02 +01:00
4e1733ba6c Fix: More reverting 2024-03-20 14:44:40 +01:00
a4e495ea1c Fix: Restructured frontend 2024-03-20 14:42:34 +01:00
a750d68363 Fix: Reverted backend changes 2024-03-20 14:40:24 +01:00
d7161a353d Fix: Better variable naming 2024-03-20 13:15:51 +01:00
12c414817f Fix: Remove debugging logs 2024-03-20 13:14:18 +01:00
e5e494d0ee Fix: Also display imported folder indicator for nested folders 2024-03-20 13:13:50 +01:00
5a21b85e9e Fix: Removed overlap from other working branch 2024-03-20 13:13:19 +01:00
348fdf6429 Feat: Visualize imported folders in overview page (include imported folders in response) 2024-03-20 12:56:11 +01:00
88e609cb66 Feat: New types for imported folders 2024-03-20 12:55:40 +01:00
78058d691a Enhancement: Add disabled prop to Tooltip component 2024-03-20 12:55:08 +01:00
1d465a50c3 Feat: Visualize imported folders in overview page 2024-03-20 12:54:44 +01:00
ffc7249c7c update diagram 2024-03-19 23:44:12 -04:00
90bcf23097 Update README.md 2024-03-19 23:36:07 -04:00
5fa4d9029d Merge pull request from Salman2301/fix-notification-z-index
fix: notification error behind detail sidebar
2024-03-19 18:56:14 -04:00
7160cf58ee Merge branch 'main' into fix-notification-z-index 2024-03-19 18:50:58 -04:00
6b2d757e39 remove outdated healthcheck 2024-03-19 17:21:46 -04:00
c075fcceca Merge pull request from Infisical/daniel/prettier-fix
Chore: Prettier formatting
2024-03-19 21:23:11 +01:00
e25f5dd65f Merge pull request from Infisical/creation-policy-k8s
add managed secret creation policy
2024-03-19 15:23:16 -04:00
3eef023c30 add managed secret creation policy 2024-03-19 14:58:17 -04:00
e63deb0860 Patch org role slug validation 2024-03-19 10:23:00 -07:00
02b2851990 Merge pull request from Infisical/fix/db-host
fix(server): updated secret rotation to pick on db host in validation
2024-03-19 10:03:12 -04:00
cb828200e1 fix(server): updated secret rotation to pick on db host in validation 2024-03-19 13:56:21 +00:00
77d068ae2c Merge pull request from Infisical/daniel/improve-create-project
Fix: Remove required org slug from create project route
2024-03-19 18:31:16 +05:30
8702af671d Fix: Typings error 2024-03-19 13:45:57 +01:00
31c0fd96ea Update UserInfoStep.tsx 2024-03-19 13:39:34 +01:00
2c539697df Feat: Remove orgSlug from create project endpoint 2024-03-19 13:39:24 +01:00
ae97b74933 Feat: Improve create project, remove organization slug from frontend 2024-03-19 13:38:58 +01:00
3e6af2dae5 Merge pull request from Infisical/daniel/api-endpoint-fix
Fix: Mintlify interactive API docs defaulting to localhost server
2024-03-19 16:32:49 +05:30
3c91e1127f Fix: Mintlify docs defaulting to localhost endpoint 2024-03-19 11:58:01 +01:00
0e31a9146a Update ee.mdx 2024-03-18 22:10:09 -07:00
fa1b28b33f Update .eslintrc.js 2024-03-18 16:07:49 +01:00
415cf31b2d Fix: Lint bug (Cannot read properties of undefined (reading 'getTokens')) 2024-03-18 16:01:14 +01:00
9002e6cb33 Fix: Format entire frontend properly 2024-03-18 16:00:03 +01:00
1ede551c3e Fix: Format entire frontend properly 2024-03-18 15:59:47 +01:00
b7b43858f6 Fix: Format entire frontend properly 2024-03-18 15:55:01 +01:00
c91789e6d0 Merge pull request from Infisical/daniel/ts-comments
Fix: Github warnings / Lint warnings
2024-03-18 20:19:26 +05:30
db0ba4be10 Fix: Github warnings / Lint warnings 2024-03-18 15:47:03 +01:00
f73c807aa0 Merge pull request from Infisical/daniel/ui-improvements
Fix: Select organization UX & project card enhancements
2024-03-18 20:14:39 +05:30
d1dacd81aa Chore: UX improvements to project cards 2024-03-18 15:36:59 +01:00
e8b635ce37 Fix: Code cleanup & truncate organization names 2024-03-18 15:36:01 +01:00
1d3e03e308 Merge pull request from Infisical/daniel/saml-cli-fix
Fix: SAML CLI login
2024-03-18 10:44:48 +01:00
88e2eff7eb Fix: SAML CLI login 2024-03-18 10:37:49 +01:00
cd192ee228 Update sdks.mdx 2024-03-17 15:04:14 -04:00
1e657968f6 Merge pull request from Infisical/daniel/slug-projects
(Feat): V2 slugified project routes
2024-03-17 13:54:20 -04:00
b8ba51512c Fix: Rebase errors 2024-03-17 18:51:19 +01:00
1ac8ddbd92 Fix: Rebase errors 2024-03-17 18:49:30 +01:00
a257743fa5 Update secret-approval-request-service.ts 2024-03-17 18:49:30 +01:00
b5a7240375 Fix: Select org when using init 2024-03-17 18:49:30 +01:00
5c2a108c52 add small helpful comment 2024-03-17 18:49:30 +01:00
b78d8d28ed Feat: CLI support for scoped JWT tokens 2024-03-17 18:49:30 +01:00
9c9ade52db Feat: Scoped JWT to organization, Add authMethod to services 2024-03-17 18:49:30 +01:00
4d229ec745 Fix: Email signup and switching organization 2024-03-17 18:49:30 +01:00
605dad29ca Fix: Rebase error 2024-03-17 18:49:30 +01:00
bebdad8159 parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345579 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345572 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345563 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345551 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345540 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345533 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345529 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345522 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345503 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345496 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345489 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345357 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345061 +0100

parent 10a292bca563efbe5972d7ffc33ee4b96a868e42
author Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1709970985 +0100
committer Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> 1710345029 +0100

Feat: Org Scoped JWT Tokens

Add link button

Fix: Avoid invalidating all queries on logout to prevent UI glitch

Update _app.tsx

Feat: Scoped JWT to organization, add authMethod to request

Feat: Scoped JWT to organization, Add authMethod to services

Feat: Scoped JWT to organization, require organization on all requests by default on JWT requests

Update index.ts

Feat: Scoped JWT to organization

Chore: Move SAML org check to permission service

Feat: Scoped JWT to organization, actorAuthMethod to create project DTO

Fix: Invalidate after selecting organization

Chore: Optional 'invalidate' option for create org hook

Fix: Creating dummy workspaces

Fix: Select org after creation

Feat: Org Scoped JWT's, remove inline service

Fix: ActorType unresolved

Fix: Better type checking

Feat: Org scoped JWT's

Fix: Add missing actor org ID

Fix: Add missing actor org ID

Fix: Return access token

Update auth-type.ts

Fix: Add actor org ID

Chore: Remove unused code

Fix: Add missing actor org ID to permission check

Fix: Add missing actor auth method to permission checks

Fix: Include actor org id

Chore: Remove redundant lint comment

Fix: Add missing actorOrgId to service handlers

Fix: Rebase fixes

Fix: Rebase LDAP fixes

Chore: Export Cli login interface

Update queries.tsx

Feat: Org scoped JWT's CLI support

Update inject-permission.ts

Fix: MFA

Remove log

Fix: Admin signup, select organization

Improvement: Use select organization hook

Update permission-service.ts

Fix: Make API keys compatible with old endpoints

Update inject-permission.ts

Chore: Better error messages

Update index.ts

Fix: Signup not redirecting to backup PDF page due to error

Select org on signup

Type improvements

Chore: Removed code that spans out of scope

Fix: Better types

Chore: Move comment

Chore: Change order

Fix: Code readability

Fix: Code readability

Update auth-token-service.ts

Chore: Remove old comments

Fix: Cleanup

Chore: Minor code cleanup

Fix: Add auth method and organization ID to test JWT

Fix: Get org ID in getOrgIdentityPermission DAL operation
2024-03-17 18:49:30 +01:00
b547309ae4 Fix: Get org ID in getOrgIdentityPermission DAL operation 2024-03-17 18:49:30 +01:00
d1ebdbcc03 Fix: Add auth method and organization ID to test JWT 2024-03-17 18:49:30 +01:00
c94caa6fb5 Chore: Minor code cleanup 2024-03-17 18:49:30 +01:00
f53fa46c51 Fix: Cleanup 2024-03-17 18:49:30 +01:00
c42d407cda Chore: Remove old comments 2024-03-17 18:49:30 +01:00
80b4bc18ec Update auth-token-service.ts 2024-03-17 18:49:30 +01:00
1dbf80d4e6 Fix: Code readability 2024-03-17 18:49:30 +01:00
700a072ec5 Fix: Code readability 2024-03-17 18:49:30 +01:00
8f42914df5 Chore: Change order 2024-03-17 18:49:30 +01:00
831da10073 Chore: Move comment 2024-03-17 18:49:30 +01:00
6904cd3bda Fix: Better types 2024-03-17 18:49:30 +01:00
52fd09b87b Chore: Removed code that spans out of scope 2024-03-17 18:49:30 +01:00
0081bbdf9e Type improvements 2024-03-17 18:49:30 +01:00
c9e5f2bb75 Select org on signup 2024-03-17 18:49:30 +01:00
73cc97cf17 Fix: Signup not redirecting to backup PDF page due to error 2024-03-17 18:49:30 +01:00
0c1d37cc75 Update index.ts 2024-03-17 18:49:30 +01:00
60fbd8ac44 Chore: Better error messages 2024-03-17 18:49:30 +01:00
36efa6ba63 Update inject-permission.ts 2024-03-17 18:49:30 +01:00
961a73f712 Fix: Re-add API key support 2024-03-17 18:49:30 +01:00
6e2f3800d4 Fix: Make API keys compatible with old endpoints 2024-03-17 18:49:30 +01:00
258c9e45d4 Update permission-service.ts 2024-03-17 18:49:30 +01:00
8573263379 Update permission-service.ts 2024-03-17 18:49:30 +01:00
9a724db6ab Improvement: Use select organization hook 2024-03-17 18:49:30 +01:00
60a37e784b Fix: member invites, select org 2024-03-17 18:49:30 +01:00
14c60bd075 Fix: Admin signup, select organization 2024-03-17 18:49:30 +01:00
de715c03ad Fix: Org scoped JWT's, MFA support 2024-03-17 18:49:30 +01:00
ddb1d5a1ab Remove log 2024-03-17 18:49:30 +01:00
41323f205d Fix: MFA 2024-03-17 18:49:30 +01:00
771498b817 Update inject-permission.ts 2024-03-17 18:49:30 +01:00
22b2fb4c98 Feat: Org scoped JWT's CLI support 2024-03-17 18:49:30 +01:00
9bbba92768 Feat: Org scoped JWT's CLI support 2024-03-17 18:49:30 +01:00
46eea972f7 Feat: Org scoped JWT's CLI support 2024-03-17 18:49:30 +01:00
9eb2a74bdf Feat: Org scoped JWT's, CLI support 2024-03-17 18:49:30 +01:00
b80579fdef Update queries.tsx 2024-03-17 18:49:30 +01:00
214894c88b Chore: Export Cli login interface 2024-03-17 18:49:29 +01:00
8ff37e3ec9 Fix: Rebase LDAP fixes 2024-03-17 18:49:29 +01:00
926f719967 Fix: Rebase fixes 2024-03-17 18:49:29 +01:00
c3a56f469a Fix: Better type checking 2024-03-17 18:49:29 +01:00
2bd9914373 Fix: Add missing actorOrgId to service handlers 2024-03-17 18:49:29 +01:00
354bac486a Fix: Don't allow org select screen when token already has an organization ID 2024-03-17 18:49:29 +01:00
ba22a7fca6 Chore: Remove redundant lint comment 2024-03-17 18:49:29 +01:00
4aef8ab8ee Fix: Include actor org id 2024-03-17 18:49:29 +01:00
e89503f00f Fix: Add missing actor auth method to permission checks 2024-03-17 18:49:29 +01:00
f5f20fbdca Fix: Add missing actor org ID to permission check 2024-03-17 18:49:29 +01:00
4d4887059a Chore: Remove unused code 2024-03-17 18:49:29 +01:00
c11c5ec85e Fix: Add actor org ID 2024-03-17 18:49:29 +01:00
f0e3c9a4b2 Update auth-type.ts 2024-03-17 18:49:29 +01:00
eace4f1bdc Fix: Return access token 2024-03-17 18:49:29 +01:00
0bd3f32c6e Fix: Add missing actor org ID 2024-03-17 18:49:29 +01:00
ad0504e957 Fix: Add missing actor org ID 2024-03-17 18:49:29 +01:00
1e20d780ec Feat: Org scoped JWT's 2024-03-17 18:49:29 +01:00
7e2685d604 Fix: Better type checking 2024-03-17 18:49:29 +01:00
92fd2d080d Fix: ActorType unresolved 2024-03-17 18:49:29 +01:00
6d60413593 Feat: Org Scoped JWT's, service handler 2024-03-17 18:49:29 +01:00
f59a75d790 Feat: Org Scoped JWT's, remove inline service 2024-03-17 18:49:29 +01:00
835c36d161 Fix: Select org after creation 2024-03-17 18:49:29 +01:00
e4dba6d5c8 Fix: Formatting and support for selecting org (line 109-122) 2024-03-17 18:49:29 +01:00
b9986be387 Fix: Creating dummy workspaces 2024-03-17 18:49:29 +01:00
5f5d62a285 Fix: Selecting SAML enforced organization 2024-03-17 18:49:29 +01:00
667fa7a9e3 Chore: Optional 'invalidate' option for create org hook 2024-03-17 18:49:29 +01:00
27dcb06083 Fix: Invalidate after selecting organization 2024-03-17 18:49:29 +01:00
9b1a15331a Fix: Creating dummy workspaces 2024-03-17 18:49:29 +01:00
65776b7ab9 Feat: Scoped JWT to organization, actorAuthMethod to create project DTO 2024-03-17 18:49:29 +01:00
a9c1f278a1 Feat: Scoped JWT to organization, add actorAuthMethod to DTO's 2024-03-17 18:49:29 +01:00
900facdb36 Feat: Scoped JWT to organization, add actorAuthMethod to services 2024-03-17 18:49:29 +01:00
fe638ce2c1 Feat: Scoped JWT to organization, add actorAuthMethod to services 2024-03-17 18:49:29 +01:00
750a43c978 Feat: Scoped JWT to organization 2024-03-17 18:48:10 +01:00
08b5975f26 Chore: Move SAML org check to permission service 2024-03-17 18:48:10 +01:00
885d1fbd7f Feat: Scoped JWT to organization 2024-03-17 18:48:10 +01:00
bb2413d659 Update index.ts 2024-03-17 18:48:10 +01:00
dac5529b6c Feat: Scoped JWT to organization, require organization on all requests by default on JWT requests 2024-03-17 18:48:10 +01:00
bd92e35729 Feat: Scoped JWT to organization, add actorAuthMethod to Permission types 2024-03-17 18:48:10 +01:00
5b7562a76d Feat: Scoped JWT to organization, Add actorAuthMethod to DTO 2024-03-17 18:48:10 +01:00
edbf459d04 Feat: Scoped JWT to organization 2024-03-17 18:48:10 +01:00
560274bde8 Feat: Scoped JWT to organization, Add authMethod to services 2024-03-17 18:48:10 +01:00
7df614a018 Feat: Scoped JWT to organization, SAML helper functions 2024-03-17 18:48:10 +01:00
47287be5bf Feat: Scoped JWT to organization, add authMethod to request 2024-03-17 18:48:10 +01:00
6e96f2338c Feat: Scoped JWT to organization, include authMethod on all service calls 2024-03-17 18:48:10 +01:00
7fd6b63b5d Feat: Navigate to select org 2024-03-17 18:48:10 +01:00
995777d76f Formatting and navigating to select org 2024-03-17 18:48:10 +01:00
2a6032a8cf Navigate to select org instead of dashboard 2024-03-17 18:48:10 +01:00
ec4d1dd1b2 Update _app.tsx 2024-03-17 18:48:10 +01:00
143de12d67 Feat: Select organization on login 2024-03-17 18:48:10 +01:00
52cf937826 Fix: Avoid invalidating all queries on logout to prevent UI glitch 2024-03-17 18:48:10 +01:00
dbd7561037 Add link button 2024-03-17 18:48:10 +01:00
d287c3e152 Feat: Org Scoped JWT Tokens 2024-03-17 18:48:10 +01:00
8fc081973d Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
c42bbbea8b Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
29b2b12ec7 Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
4f80234afa Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
a1fa0c652d Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
8327f41b8e Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
c2bfeb89e8 Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
4a0668e92e Feat: Org Scoped JWT Tokens 2024-03-17 18:48:09 +01:00
716e705c2a Fix: Removed legacy create project code 2024-03-17 18:48:09 +01:00
f860fd3abe Update project-types.ts 2024-03-17 18:48:09 +01:00
30e7fe8a45 Fix: Rebase errors 2024-03-17 18:48:09 +01:00
307b89e799 Update inject-identity.ts 2024-03-17 18:48:09 +01:00
dbf498b44a Fix: Remove orgId from service token 2024-03-17 18:48:09 +01:00
5eb3258311 Fix: Remove org ID from JWT 2024-03-17 18:48:09 +01:00
bd3cbb3c7b feat: fix project query by slug (now accepts an org ID) 2024-03-17 18:48:09 +01:00
96abbd9f80 feat: standardize org ID's on auth requests 2024-03-17 18:48:09 +01:00
92441e018f Slug projects and filter type 2024-03-17 18:48:09 +01:00
a9bba02f44 Draft 2024-03-17 18:48:09 +01:00
aaca3ac229 Fix: Change org ID to org slug 2024-03-17 18:48:09 +01:00
f0383dd55c Fix: Change org ID to org slug 2024-03-17 18:48:09 +01:00
a766329de5 Fix: Non-existant variable being passed to Posthog 2024-03-17 18:48:09 +01:00
c0b0c0754b Feat: List secrets by project slug 2024-03-17 18:48:09 +01:00
34618041ca Update inject-identity.ts 2024-03-17 18:48:09 +01:00
f36a056c62 Update inject-identity.ts 2024-03-17 18:48:09 +01:00
e7b11eac2b Fix: Remove orgId from service token 2024-03-17 18:48:09 +01:00
0f14fab915 Update index.ts 2024-03-17 18:48:09 +01:00
12a6fba645 Feat: Create project via org slug instead of org ID 2024-03-17 18:48:09 +01:00
ce057f44ac nit: update error message 2024-03-17 18:48:09 +01:00
2032063c24 Fix: Remove org ID from JWT 2024-03-17 18:48:09 +01:00
bbceb37d06 feat: fix project query by slug (now accepts an org ID) 2024-03-17 18:48:09 +01:00
e917b744f4 feat: standardize org ID's on auth requests 2024-03-17 18:48:09 +01:00
7438c114dd Remove API key auth mode 2024-03-17 18:48:09 +01:00
8e3fc044ca Slug projects and filter type 2024-03-17 18:48:09 +01:00
9473de2212 Draft 2024-03-17 18:48:09 +01:00
744c510a51 Merge pull request from Infisical/render-integration-auto-deploy
Render integration auto-redeployment capability on secret sync
2024-03-16 16:56:54 -07:00
f845749a4d Complete render integration auto-redeploy feature 2024-03-16 16:08:59 -07:00
56fc5a2a8c Merge pull request from Salman2301/feat-github-integration
Github Integrations
2024-03-15 20:44:09 -07:00
7edebbabaa Update docs for github integration 2024-03-15 20:38:02 -07:00
0e698e9355 Clean GitHub integration 2024-03-15 20:25:04 -07:00
ee215bccfa fix: notification error behind detail sidebar 2024-03-16 08:34:21 +05:30
00b99e7255 fix: remove purple banner screenshot 2024-03-16 08:14:29 +05:30
2b7784718d fix: disabled repo env until repo selected 2024-03-16 08:01:28 +05:30
5f99e58674 Update github integration org/envs support 2024-03-15 18:45:55 -07:00
f77942c702 Fix merge conflicts 2024-03-15 13:07:29 -07:00
2d3fddd0e9 Merge pull request from Infisical/daniel/api-endpoint-docs
Feat: Documentation improvements
2024-03-14 16:34:00 -07:00
519b92d592 Update mint.json 2024-03-14 19:05:34 +01:00
c3d5e882f8 Merge pull request from Infisical/aws-ps-integration
Remove recursive behavior in AWS PS integration
2024-03-14 10:36:16 -07:00
4c354eb3ea Remove recursive behavior in AWS PS integration 2024-03-14 10:32:05 -07:00
97eff2b480 Fix: Moved categories and renamed Libs -> SDKs 2024-03-14 18:04:27 +01:00
c621592807 Add .pem loader to tsup 2024-03-14 09:31:58 -07:00
bd400a6196 Merge pull request from Infisical/render-integration
Add pagination to getAppsRender
2024-03-14 09:30:42 -07:00
a93c2d9236 Add pagination to getAppsRender 2024-03-14 09:24:04 -07:00
11dfeda501 Fix: No nested groups 2024-03-14 17:13:18 +01:00
70bd64d54b Fix: Shorter sidebar titles 2024-03-14 17:09:45 +01:00
0c88a5466c Feat: Documentation improvements 2024-03-14 17:09:37 +01:00
36266b30d5 Fix: Shorter sidebar title 2024-03-14 17:09:21 +01:00
288577b455 Fix: OpenAPI specification URL 2024-03-14 15:41:32 +01:00
5194be14fd Update mint.json 2024-03-14 15:40:53 +01:00
bab8f95fde Feat: Added descriptions to all public API endpoints 2024-03-14 15:40:38 +01:00
b4f372f883 Fix: Delete folder docs page not rendering 2024-03-14 15:40:08 +01:00
b13365ecf5 Feat: Written API explanations 2024-03-14 15:39:29 +01:00
bb6e09a895 Merge pull request from Infisical/license-docs
Add simple docs for Infisical EE
2024-03-13 18:07:46 -07:00
715b193a8e Add simple docs for Infisical EE 2024-03-13 18:05:13 -07:00
57be493da8 Merge pull request from Infisical/offline-license
Add EE license support for air-gapped / offline environments
2024-03-13 17:25:35 -07:00
cc731fe031 Add EE license support for air-gapped / offline environments 2024-03-13 17:18:55 -07:00
70618420d7 Merge pull request from Infisical/daniel/deprecate-service-tokens-and-api-keys
Feat: Deprecate API keys
2024-03-13 17:58:05 +01:00
7feb7ef9c6 Fix: Change title 2024-03-13 17:52:24 +01:00
ab1b9fb164 Feat: Deprecate API keys 2024-03-13 17:46:48 +01:00
8c028889a6 Chore: Remove unused frontend V2 API key code 2024-03-13 17:45:19 +01:00
7dc366baf0 Merge pull request from rhythmbhiwani/fix-typo
Fix typo on Copy button tooltip
2024-03-13 16:28:42 +05:30
2124d2669f Merge pull request from myapos/bugfix_1563_remove_member
fix issue on removing member from project
2024-03-13 16:24:18 +05:30
af83fbea14 fix issue on removing member from project 2024-03-13 12:32:11 +02:00
9657b64ab2 Fix typo on Copy button tooltip 2024-03-13 15:54:42 +05:30
90b55a94e1 Merge pull request from Infisical/pg-new-connection
add individual pg connection properties
2024-03-13 00:43:45 -04:00
d83d249f29 add individual pg connection properties 2024-03-13 00:40:37 -04:00
151787c60a Merge pull request from Infisical/toggle-invite-org
Update UI for Org Settings Page
2024-03-12 16:26:39 -07:00
ce443b114c Fix merge conflicts 2024-03-12 16:20:17 -07:00
2ca03abec2 Update org settings > general tab ui 2024-03-12 16:18:17 -07:00
c8bb690736 Merge pull request from akhilmhdh/feat/secret-rotation-aws-iam
Secret rotation for AWS IAM User
2024-03-12 15:36:49 -04:00
6efbdaef9c Update docs for AWS IAM rotation strategy 2024-03-12 12:06:42 -07:00
7e90493cce Merge pull request from akhilmhdh/feat/temp-roles
feat(server): removed stream in project multi role migration
2024-03-12 13:37:09 -04:00
1330c0455a feat(server): removed stream in project multi role migration 2024-03-12 23:06:06 +05:30
407248c616 Merge pull request from akhilmhdh/feat/temp-roles
feat: multiple project role and temporary role support
2024-03-12 12:57:00 -04:00
a6d7d32156 Draft revamp org auth tab 2024-03-12 09:37:28 -07:00
0f0e2b360c feat(server): rebased migration files and resolved commits 2024-03-12 21:57:45 +05:30
47906c4dd4 feat(server): removed role and roleid field dropping from project membership table for rolling forward migration 2024-03-12 21:50:39 +05:30
fc57884035 renamed from temp access to timed access 2024-03-12 21:49:33 +05:30
4152b3a524 feat: minor bug fixes and text changes 2024-03-12 21:49:33 +05:30
f1f18e81cd feat(ui): added identity multi project role and temporary in ui 2024-03-12 21:49:33 +05:30
929f91a738 feat(server): multi role with temporary support for identity 2024-03-12 21:49:33 +05:30
fa41b8bb47 feat(ui): completed ui user multi role with temporary access 2024-03-12 21:49:33 +05:30
edbb7e2b1e feat(server): completed user multi role with temporary access 2024-03-12 21:39:37 +05:30
1d53e0f21b Merge pull request from rhythmbhiwani/overview-page-enchanced
Overview Page Bug Fixes and Enhancement
2024-03-12 11:58:42 -04:00
a232450f20 Resolved Comments 2024-03-12 21:21:29 +05:30
6f65f2a63d Merge pull request from Grraahaam/doc/cli-history
doc(cli): HISTIGNORE recommendation
2024-03-12 19:03:46 +05:30
9545960e6f Merge pull request from Infisical/daniel/cli-expand-secrets-fix
Fix: CLI user authentication
2024-03-12 15:32:00 +05:30
cfa42017b1 Fix: CLI expanding secrets 2024-03-12 10:55:36 +01:00
1b74fdb232 Fix: CLI expanding secrets 2024-03-12 10:55:30 +01:00
ad1cae6aac Merge pull request from Salman2301/fix-org-long-name
fix: truncate too long proj or org name
2024-03-12 10:22:06 +01:00
e5d4328e2a Merge branch 'heads/main' into overview-page-enchanced 2024-03-12 11:11:20 +05:30
635948c4f4 Update overview.mdx 2024-03-11 19:19:04 -07:00
d6231d4649 Update overview.mdx 2024-03-11 19:16:57 -07:00
041535bb47 Merge pull request from Infisical/ldap
Add support for LDAP authentication + Aliases
2024-03-11 17:21:58 -07:00
3f0c4f0ca9 fix: lowercase and remove truncate for button 2024-03-12 03:42:38 +05:30
5c8b886d7b Merge remote-tracking branch 'origin' into ldap 2024-03-11 14:20:45 -07:00
51a5bf8181 Update LDAP migration to latest 2024-03-11 14:20:32 -07:00
822d0692db Merge pull request from rhythmbhiwani/fix-secret-page-with-no-env
Redirecting to overview page if environment doesn't exists in secrets main page
2024-03-11 11:32:45 +01:00
e527d99654 Remove console.log 2024-03-11 15:42:18 +05:30
628c641580 Merge pull request from Infisical/daniel/cli-hotfix
Fix: CLI Service Tokens
2024-03-11 10:04:32 +01:00
40ccab6576 Fix: Failing to get service token 2024-03-11 09:05:56 +01:00
9cc3e58561 Fix: Failing to get service token 2024-03-11 09:05:54 +01:00
1f3fded404 Fix: Failing to get service token 2024-03-11 09:05:52 +01:00
74b5e8cbeb Fix: Failing to get service token 2024-03-11 09:05:47 +01:00
522a03c2ad Fix: Helper function for getting service token 2024-03-11 09:05:33 +01:00
624fb3d46a Fixed issue when visible environments not updating on project change 2024-03-11 10:04:47 +05:30
8a27b1b5e6 Fixed overflow of environment name in SecretOverviewTableRow 2024-03-11 09:43:00 +05:30
56bf82e4f6 Redirecting to overview page if environment doesn't exists in secrets main page 2024-03-11 06:52:37 +05:30
972b80e790 updated message if search results not found 2024-03-11 06:11:26 +05:30
6cc0d79d8a Fixed environment filter button height
Polished the interface more
2024-03-11 06:01:31 +05:30
163ccd6cdb Update username migration file 2024-03-10 15:03:43 -07:00
06f3a6d262 Make LDAP config fields not nullable 2024-03-10 14:44:18 -07:00
b641bbf229 Update test user username back to email-based 2024-03-10 12:47:26 -07:00
feb7563eab Merge remote-tracking branch 'origin' into ldap 2024-03-10 12:31:06 -07:00
7594929042 Separate ldap boot/parent wrapper logic, move ldap services into docker compose profile, update ldap form logic to use zod 2024-03-10 12:28:50 -07:00
f1b7653a52 Merge pull request from Infisical/integration-update-heroku
Integration update heroku
2024-03-09 19:15:00 -08:00
0cb6d052e0 Update values.yaml 2024-03-09 20:31:11 -05:00
ceb135fc94 Merge pull request from Infisical/snyk-upgrade-ba8f3acf185100a451cbbadcbe68f789
[Snyk] Upgrade posthog-js from 1.105.4 to 1.105.6
2024-03-09 11:05:45 -05:00
b75289f074 Merge branch 'main' into snyk-upgrade-ba8f3acf185100a451cbbadcbe68f789 2024-03-09 11:05:36 -05:00
de86705e64 Merge pull request from rhythmbhiwani/feature-rename-secret-accross-envs
Feature: Rename secret from overview page, accross all environments
2024-03-09 11:02:22 -05:00
f9b6f78e8d fix(server): resolved broken required validation in aws iam template 2024-03-09 18:26:16 +05:30
2852a495c8 docs: aws iam secret rotation 2024-03-09 18:24:22 +05:30
6ca56143d9 Merge pull request from rhythmbhiwani/docs-typo-fixed-cli
Fixed typo in `secrets get` docs
2024-03-09 17:32:45 +05:30
ef0e652557 Fixed typo in secrets get docs 2024-03-09 15:23:25 +05:30
89e109e404 Iron out naming / text, update docs for Heroku integration 2024-03-08 18:12:52 -08:00
48062d9680 Merge pull request from akhilmhdh/fix/create-folder-cli
feat(server): added back delete by folder name in api
2024-03-08 17:46:14 -05:00
d11fda3be5 Merge pull request from Infisical/railway-integration
Update Railway integration get services query, make services optional
2024-03-08 14:25:14 -08:00
0df5f845fb Update docker-swarm-with-agent.mdx 2024-03-08 17:07:11 -05:00
ca59488b62 Update Railway integration get services query, make services optional 2024-03-08 11:46:51 -08:00
3a05ae4b27 Merge pull request from Infisical/docker-swarm-docs
docs: docker swarm with infisical agent
2024-03-08 14:42:56 -05:00
dd009182e8 docs: docker swarm with infisical agent 2024-03-08 14:42:02 -05:00
8ac7a29893 Draft refactor core secrets fn into reusable factories 2024-03-08 09:06:03 -08:00
8a17cd3f5d Merge pull request from rhythmbhiwani/get-only-value-from-cli
Feature to get only value of specific secret in `secrets get` command
2024-03-08 10:54:10 -05:00
99fe43f459 rename --value to --raw-value + polish docs 2024-03-08 10:53:11 -05:00
2e3b10ccfc feat(server): added back delete by folder name in api 2024-03-08 17:45:14 +05:30
79196b0081 Update secret-reference.mdx 2024-03-08 00:19:10 -05:00
b76ff28414 Update secret-reference.mdx 2024-03-08 00:16:48 -05:00
2894cf791a Merge pull request from Infisical/daniel/agent-template-func
Feat: Agent secret referencing support
2024-03-08 00:04:29 -05:00
c040b0ca9a Fix: Include Workspace ID in request when expanding secrets with MI's 2024-03-08 06:02:44 +01:00
15f60aa7dd Fix: Add WorkspaceID field to env variable struct 2024-03-08 06:02:29 +01:00
6f68d304ea Fix: Get Service Token from env vars earlier 2024-03-08 06:02:15 +01:00
0b98feea50 Make sync behavior apply on first sync only, finish MVP create/update bidirectional sync for Heroku 2024-03-07 18:06:56 -08:00
43d40d7475 MVP preliminary idea for initial sync behavior 2024-03-07 15:25:53 -08:00
309a106f13 patch create folder on cli 2024-03-07 17:02:35 -05:00
74d73590a1 add docker manifest to go releaser 2024-03-07 15:38:01 -05:00
b42b5614c9 add buildx to workflow 2024-03-07 15:21:16 -05:00
72b89cb989 try buildx to support multi arch 2024-03-07 15:14:14 -05:00
8c491668dc docs: updated images of inputs in secret rotation 2024-03-07 23:17:32 +05:30
6305300b12 feat(ui): image for aws iam secret rotation 2024-03-07 14:51:14 +05:30
b4ae1e8f3b feat(server): added aws iam secret rotation function 2024-03-07 14:50:42 +05:30
36d8b22598 Feat: Agent secret referencing support 2024-03-07 07:03:16 +01:00
201dcd971c Feat: Agent secret referencing support 2024-03-07 06:49:57 +01:00
ab90745312 Feat: Agent secret referencing support (Auth input) 2024-03-07 06:49:28 +01:00
622106045e Feat: Agent secret referencing support (update ExpandSecrets input) 2024-03-07 06:48:52 +01:00
e64302b789 Feat: Agent secret referencing support (update ExpandSecrets input) 2024-03-07 06:48:48 +01:00
901a7fc294 Feat: Agent secret referencing support (update ExpandSecrets input) 2024-03-07 06:48:43 +01:00
359694dd47 Chore: Cleanup 2024-03-07 06:48:08 +01:00
57489a7578 Merge pull request from Infisical/daniel/copy-project-slug
Feat: Copy project slug button
2024-03-07 02:27:24 +01:00
a4205a8662 Cleanup 🧼 2024-03-07 02:22:52 +01:00
dbf177d667 Feat: Add copy project slug button 2024-03-07 02:20:01 +01:00
f078aec54c Feat: Add copy project slug button 2024-03-07 02:19:54 +01:00
5dfe62e306 Feat: Copy project slug button 2024-03-07 02:01:31 +01:00
b89925c61c Feat: Copy project slug button 2024-03-07 02:01:23 +01:00
440a58a49b Fix merge conflicts 2024-03-06 15:48:32 -08:00
6d0bea6d5f Update .goreleaser.yaml to support arm 2024-03-06 18:42:27 -05:00
10a40c8ab2 Merge pull request from Infisical/daniel/better-upgrade-errors
Fix: Edge case causing project upgrade to fail
2024-03-07 00:07:05 +01:00
b910ceacfc create secret on overview typo 2024-03-06 17:50:31 -05:00
cb66386e13 Merge pull request from Infisical/daniel/fix-project-memberships
Fix: Remove project keys & memberships when organization membership is deleted
2024-03-06 17:38:49 -05:00
ae53f03f71 Fix: Remove project memberships & project keys when org membership is deleted (DAL) 2024-03-06 23:15:13 +01:00
7ae024724d Fix: Remove project memberships & project keys when org membership is deleted (Service) 2024-03-06 23:15:02 +01:00
0b2bc1d345 Fix: Remove project memberships & project keys when org membership is deleted 2024-03-06 23:14:52 +01:00
da5eca3e68 Fix: Seeding not working 2024-03-06 23:13:22 +01:00
35a5c9a67f Fix lint issue 2024-03-06 13:28:29 -08:00
7d495cfea5 Correct frontend email to username in AppLayout 2024-03-06 13:14:50 -08:00
2eca9d8200 Check again email traces 2024-03-06 12:40:27 -08:00
4d707eee8a Fix frontend type issues 2024-03-06 12:17:18 -08:00
76bd85efa7 Add user aliases concept and weave LDAP into it 2024-03-06 12:06:40 -08:00
d140e4f3c9 update bulk add message 2024-03-06 14:12:21 -05:00
327c5e2429 Update migration file to latest, remove git markers 2024-03-05 08:59:58 -08:00
f29dd6effa Fix merge conflicts 2024-03-05 08:54:24 -08:00
8e25631fb0 Updated the docs 2024-03-05 16:14:20 +05:30
0912903e0d Added --value flag to secrets get command to return only value 2024-03-05 16:04:21 +05:30
c873e2cba8 docs: updated secret rotation doc with images 2024-03-05 15:42:53 +05:30
1bc045a7fa update overview sendgrid 2024-03-05 15:42:53 +05:30
533de93199 docs: improved secret rotation documentation with better understanding 2024-03-05 15:42:53 +05:30
d8860e1ce3 Disabled submit button when renaming all keys if key name is empty 2024-03-03 02:49:35 +05:30
3fa529dcb0 Added error message if name is empty 2024-03-02 09:30:03 +05:30
b6f3cf512e spacing made consistent 2024-03-02 06:57:36 +05:30
4dbee7df06 Added notification on success and failure renaming secret 2024-03-02 06:45:52 +05:30
323c412f5e Added Option to Rename Secrets from overview page in all environments 2024-03-02 06:41:32 +05:30
c2fe6eb90c fix: upgrade posthog-js from 1.105.4 to 1.105.6
Snyk has created this PR to upgrade posthog-js from 1.105.4 to 1.105.6.

See this package in npm:
https://www.npmjs.com/package/posthog-js

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-01 09:12:34 +00:00
db9f21be87 fix: add input max length for org and project name 2024-02-28 15:59:23 +05:30
449617d271 fix: truncate too long proj or org name 2024-02-28 14:38:13 +05:30
3641875b24 Update LDAP permissioning, styling of org auth section 2024-02-27 11:36:35 -08:00
a04a9a1bd3 Fix frontend lint issues 2024-02-26 20:21:09 -08:00
04d729df92 Update seed username 2024-02-26 20:05:40 -08:00
5ca1b1d77e Fix type/lint issues 2024-02-26 20:01:44 -08:00
2d9526ad8d Fix type/lint issues 2024-02-26 20:01:30 -08:00
768cc64af6 Fix merge conflicts 2024-02-26 18:28:13 -08:00
a28431bfe7 Finish preliminary LDAP 2024-02-26 17:42:32 -08:00
91068229bf Minor LDAP patches, docs for JumpCloud LDAP 2024-02-26 17:04:01 -08:00
9ba4b939a4 Add orgId to reuse login1/login2 logic for LDAP 2nd step login 2024-02-26 10:41:44 -08:00
1c088b3a58 Merge remote-tracking branch 'origin' into ldap 2024-02-25 18:24:28 -08:00
a33c50b75a Adjust SCIM and SAML impl to use username / nameID, patch LDAP edge-cases 2024-02-25 18:16:26 -08:00
8c31566e17 Update various SSO / SAML auth methods to support username 2024-02-24 22:17:51 -08:00
bfee74ff4e Add username field to users 2024-02-23 17:30:49 -08:00
97a7b66c6c Fix merge conflicts 2024-02-23 10:40:27 -08:00
639c78358f Add docs for LDAP 2024-02-23 10:33:48 -08:00
5053069bfc Finish frame for LDAP auth 2024-02-23 10:00:30 -08:00
6b92a5f4db Update delete key set to org name for github org integration 2024-02-20 04:28:59 +05:30
81e961e8bc Update remove group field for github integration section 2024-02-19 17:47:23 +05:30
6a7a6ce942 Update github integrations docs based on diff scope 2024-02-19 11:39:08 +05:30
b1d049c677 added ability to hide environments in the overview screen 2024-02-17 21:49:17 -08:00
9012012503 added basic heroku pipeline integration 2024-02-17 21:04:26 -08:00
a8678c14e8 updated heroku integration style 2024-02-16 22:58:56 -08:00
1695412278 Update integration-sync-secret for org and env 2024-02-16 04:46:41 +05:30
b4fa07334d Update use repo id to avoid ambiguous repo name 2024-02-16 02:53:39 +05:30
29c244c635 Update repo name display owner/repo instead just the repo 2024-02-16 02:13:16 +05:30
b80a5989a8 Fix reset env on repo change 2024-02-16 00:58:10 +05:30
dc696f8932 Update integration section for github repo, org and env 2024-02-15 15:59:18 +05:30
c8f0796952 Update github integrations ui for organization and environment 2024-02-15 09:41:57 +05:30
9282dd08d9 Merge remote-tracking branch 'origin/main' into feat-github-integration 2024-02-15 00:00:05 +05:30
df459d456a Update github form ui add scope and init org api 2024-02-14 04:21:50 +05:30
c8cfb43316 Update github integration refactored to react-hook-form 2024-02-13 05:42:29 +05:30
541fa10964 doc: HISTIGNORE recommendation 2024-02-04 21:18:10 +01:00
615 changed files with 17734 additions and 7609 deletions
.github
.goreleaser.yamlDockerfile.standalone-infisicalMakefileREADME.md
backend
e2e-test
package-lock.jsonpackage.json
src
@types
db
ee
lib
server
services
tsup.config.js
cli/packages
docker-compose.dev.yml
docs
frontend
.eslintrc.js
.storybook
package-lock.jsonpackage.json
public/images/secretRotation
src
components
analytics
basic
context/Notifications
dashboard
navigation
permissions
signup
utilities
v2
config
context
AuthContext
OrgPermissionContext
ProjectPermissionContext
ServerConfigContext
index.tsx
ee
helpers
hoc/withPermission
hooks/api
i18n.ts
layouts
AdminLayout
AppLayout
lib/fn
pages
services
views
IntegrationsPage
Login
Org
Project
SecretApprovalPage/components/SecretApprovalRequest/components
SecretMainPage
SecretOverviewPage
SecretRotationPage
SecretScanning/components
Settings
BillingSettingsPage
OrgSettingsPage
PersonalSettingsPage
ProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
DeleteProjectSection
E2EESection
EnvironmentSection
ProjectGeneralTab
ProjectNameChangeSection
SecretTagsSection
index.tsx
Signup
admin/SignUpPage
SignUpPage.tsx
components/DownloadBackupKeys
helm-charts/secrets-operator
k8-operator
package-lock.jsonpackage.json

2
.github/values.yaml vendored

@ -27,7 +27,7 @@ infisical:
deploymentAnnotations:
secrets.infisical.com/auto-reload: "true"
kubeSecretRef: "infisical-gamma-secrets"
kubeSecretRef: "managed-secret"
ingress:
## @param ingress.enabled Enable ingress

@ -0,0 +1,114 @@
name: Build, Publish and Deploy to Gamma
on: [workflow_dispatch]
jobs:
infisical-image:
name: Build backend image
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_FOR_ECR }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_FOR_ECR }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
# - name: 🧪 Run tests
# run: npm run test:ci
# working-directory: backend
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build backend and export to Docker
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
load: true
context: .
file: Dockerfile.standalone-infisical
tags: infisical/infisical:test
- name: 🏗️ Build backend and push to docker hub
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: .
file: Dockerfile.standalone-infisical
tags: |
infisical/staging_infisical:${{ steps.commit.outputs.short }}
infisical/staging_infisical:latest
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
postgres-migration:
name: Run latest migration files
runs-on: ubuntu-latest
needs: [infisical-image]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2
with:
node-version: "20"
- name: Change directory to backend and install dependencies
env:
DB_CONNECTION_URI: ${{ secrets.AWS_DB_CONNECTION_URI }}
run: |
cd backend
npm install
npm run migration:latest
gamma-deployment:
name: Deploy to gamma
runs-on: ubuntu-latest
needs: [postgres-migration]
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
audience: sts.amazonaws.com.cn
aws-region: us-east-1
role-to-assume: arn:aws:iam::135906656851:role/github-action-deploy-prod
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition
id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: infisical-prod-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform
cluster: infisical-prod-platform

@ -8,6 +8,15 @@ jobs:
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_FOR_ECR }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_FOR_ECR }}
aws-region: us-east-1
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
@ -35,16 +44,7 @@ jobs:
context: .
file: Dockerfile.standalone-infisical
tags: infisical/infisical:test
# - name: ⏻ Spawn backend container and dependencies
# run: |
# docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
# - name: 🧪 Test backend image
# run: |
# ./.github/resources/healthcheck.sh infisical-backend-test
# - name: ⏻ Shut down backend container and dependencies
# run: |
# docker compose -f .github/resources/docker-compose.be-test.yml down
- name: 🏗️ Build backend and push
- name: 🏗️ Build backend and push to docker hub
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
@ -59,6 +59,8 @@ jobs:
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
INFISICAL_PLATFORM_VERSION=${{ steps.extract_version.outputs.version }}
postgres-migration:
name: Run latest migration files
runs-on: ubuntu-latest

@ -23,6 +23,8 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- run: git fetch --force --tags
- run: echo "Ref name ${{github.ref_name}}"
- uses: actions/setup-go@v3

@ -190,10 +190,34 @@ dockers:
- dockerfile: docker/alpine
goos: linux
goarch: amd64
use: buildx
ids:
- all-other-builds
image_templates:
- "infisical/cli:{{ .Version }}"
- "infisical/cli:{{ .Major }}.{{ .Minor }}"
- "infisical/cli:{{ .Major }}"
- "infisical/cli:latest"
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
- "infisical/cli:latest-amd64"
build_flag_templates:
- "--pull"
- "--platform=linux/amd64"
- dockerfile: docker/alpine
goos: linux
goarch: amd64
use: buildx
ids:
- all-other-builds
image_templates:
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
- "infisical/cli:latest-arm64"
build_flag_templates:
- "--pull"
- "--platform=linux/arm64"
docker_manifests:
- name_template: "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}"
image_templates:
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64"
- "infisical/cli:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64"
- name_template: "infisical/cli:latest"
image_templates:
- "infisical/cli:latest-amd64"
- "infisical/cli:latest-arm64"

@ -118,9 +118,6 @@ WORKDIR /backend
ENV TELEMETRY_ENABLED true
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js
EXPOSE 8080
EXPOSE 443

@ -7,6 +7,9 @@ push:
up-dev:
docker compose -f docker-compose.dev.yml up --build
up-dev-ldap:
docker compose -f docker-compose.dev.yml --profile ldap up --build
up-prod:
docker-compose -f docker-compose.prod.yml up --build

@ -10,7 +10,8 @@
<a href="https://infisical.com/">Infisical Cloud</a> |
<a href="https://infisical.com/docs/self-hosting/overview">Self-Hosting</a> |
<a href="https://infisical.com/docs/documentation/getting-started/introduction">Docs</a> |
<a href="https://www.infisical.com">Website</a>
<a href="https://www.infisical.com">Website</a> |
<a href="https://infisical.com/careers">Hiring (Remote/SF)</a>
</h4>
<p align="center">

@ -10,7 +10,7 @@ import { seedData1 } from "@app/db/seed-data";
import { initEnvConfig } from "@app/lib/config/env";
import { initLogger } from "@app/lib/logger";
import { main } from "@app/server/app";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { mockQueue } from "./mocks/queue";
import { mockSmtpServer } from "./mocks/smtp";
@ -52,6 +52,8 @@ export default {
authTokenType: AuthTokenType.ACCESS_TOKEN,
userId: seedData1.id,
tokenVersionId: seedData1.token.id,
authMethod: AuthMethod.EMAIL,
organizationId: seedData1.organization.id,
accessVersion: 1
},
cfg.AUTH_SECRET,

1029
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -70,6 +70,7 @@
"vitest": "^1.2.2"
},
"dependencies": {
"@aws-sdk/client-iam": "^3.525.0",
"@aws-sdk/client-secrets-manager": "^3.504.0",
"@casl/ability": "^6.5.0",
"@fastify/cookie": "^9.3.1",
@ -106,6 +107,7 @@
"knex": "^3.0.1",
"libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0",
"ms": "^2.1.3",
"mysql2": "^3.9.1",
"nanoid": "^5.0.4",
"nodemailer": "^6.9.9",
@ -113,7 +115,9 @@
"passport-github": "^1.1.0",
"passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0",
"passport-ldapauth": "^3.0.1",
"pg": "^8.11.3",
"pg-query-stream": "^4.5.3",
"picomatch": "^3.0.1",
"pino": "^8.16.2",
"posthog-node": "^3.6.2",

@ -3,6 +3,7 @@ import "fastify";
import { TUsers } from "@app/db/schemas";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
@ -18,7 +19,7 @@ import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { TAuthLoginFactory } from "@app/services/auth/auth-login-service";
import { TAuthPasswordFactory } from "@app/services/auth/auth-password-service";
import { TAuthSignupFactory } from "@app/services/auth/auth-signup-service";
import { ActorType } from "@app/services/auth/auth-type";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
@ -58,6 +59,7 @@ declare module "fastify" {
// identity injection. depending on which kinda of token the information is filled in auth
auth: TAuthMode;
permission: {
authMethod: ActorAuthMethod;
type: ActorType;
id: string;
orgId?: string;
@ -69,6 +71,7 @@ declare module "fastify" {
};
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
}
interface FastifyInstance {
@ -107,6 +110,7 @@ declare module "fastify" {
snapshot: TSecretSnapshotServiceFactory;
saml: TSamlConfigServiceFactory;
scim: TScimServiceFactory;
ldap: TLdapConfigServiceFactory;
auditLog: TAuditLogServiceFactory;
secretScanning: TSecretScanningServiceFactory;
license: TLicenseServiceFactory;

@ -32,6 +32,9 @@ import {
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate,
TIdentityProjectMembershipRole,
TIdentityProjectMembershipRoleInsert,
TIdentityProjectMembershipRoleUpdate,
TIdentityProjectMemberships,
TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate,
@ -50,6 +53,9 @@ import {
TIntegrations,
TIntegrationsInsert,
TIntegrationsUpdate,
TLdapConfigs,
TLdapConfigsInsert,
TLdapConfigsUpdate,
TOrganizations,
TOrganizationsInsert,
TOrganizationsUpdate,
@ -80,6 +86,9 @@ import {
TProjects,
TProjectsInsert,
TProjectsUpdate,
TProjectUserMembershipRoles,
TProjectUserMembershipRolesInsert,
TProjectUserMembershipRolesUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
@ -161,6 +170,9 @@ import {
TUserActions,
TUserActionsInsert,
TUserActionsUpdate,
TUserAliases,
TUserAliasesInsert,
TUserAliasesUpdate,
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
TUserEncryptionKeysUpdate,
@ -175,6 +187,7 @@ import {
declare module "knex/types/tables" {
interface Tables {
[TableName.Users]: Knex.CompositeTableType<TUsers, TUsersInsert, TUsersUpdate>;
[TableName.UserAliases]: Knex.CompositeTableType<TUserAliases, TUserAliasesInsert, TUserAliasesUpdate>;
[TableName.UserEncryptionKey]: Knex.CompositeTableType<
TUserEncryptionKeys,
TUserEncryptionKeysInsert,
@ -214,6 +227,11 @@ declare module "knex/types/tables" {
TProjectEnvironmentsUpdate
>;
[TableName.ProjectBot]: Knex.CompositeTableType<TProjectBots, TProjectBotsInsert, TProjectBotsUpdate>;
[TableName.ProjectUserMembershipRole]: Knex.CompositeTableType<
TProjectUserMembershipRoles,
TProjectUserMembershipRolesInsert,
TProjectUserMembershipRolesUpdate
>;
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
@ -265,6 +283,11 @@ declare module "knex/types/tables" {
TIdentityProjectMembershipsInsert,
TIdentityProjectMembershipsUpdate
>;
[TableName.IdentityProjectMembershipRole]: Knex.CompositeTableType<
TIdentityProjectMembershipRole,
TIdentityProjectMembershipRoleInsert,
TIdentityProjectMembershipRoleUpdate
>;
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
TSecretApprovalPolicies,
@ -318,6 +341,7 @@ declare module "knex/types/tables" {
TSecretSnapshotFoldersUpdate
>;
[TableName.SamlConfig]: Knex.CompositeTableType<TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate>;
[TableName.LdapConfig]: Knex.CompositeTableType<TLdapConfigs, TLdapConfigsInsert, TLdapConfigsUpdate>;
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
[TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>;
[TableName.GitAppInstallSession]: Knex.CompositeTableType<

@ -6,6 +6,13 @@ export const initDbConnection = ({ dbConnectionUri, dbRootCert }: { dbConnection
client: "pg",
connection: {
connectionString: dbConnectionUri,
host: process.env.DB_HOST,
// @ts-expect-error I have no clue why only for the port there is a type error
// eslint-disable-next-line
port: process.env.DB_PORT,
user: process.env.DB_USER,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
ssl: dbRootCert
? {
rejectUnauthorized: true,

@ -7,18 +7,22 @@ import path from "path";
// Update with your config settings. .
dotenv.config({
path: path.join(__dirname, "../../../.env.migration"),
debug: true
path: path.join(__dirname, "../../../.env.migration")
});
dotenv.config({
path: path.join(__dirname, "../../../.env"),
debug: true
path: path.join(__dirname, "../../../.env")
});
export default {
development: {
client: "postgres",
connection: {
connectionString: process.env.DB_CONNECTION_URI,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
ssl: process.env.DB_ROOT_CERT
? {
rejectUnauthorized: true,
@ -41,6 +45,11 @@ export default {
client: "postgres",
connection: {
connectionString: process.env.DB_CONNECTION_URI,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
ssl: process.env.DB_ROOT_CERT
? {
rejectUnauthorized: true,

@ -0,0 +1,15 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Integration, (t) => {
t.datetime("lastUsed");
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.alterTable(TableName.Integration, (t) => {
t.dropColumn("lastUsed");
});
}

@ -0,0 +1,68 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.LdapConfig))) {
await knex.schema.createTable(TableName.LdapConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("orgId").notNullable().unique();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.boolean("isActive").notNullable();
t.string("url").notNullable();
t.string("encryptedBindDN").notNullable();
t.string("bindDNIV").notNullable();
t.string("bindDNTag").notNullable();
t.string("encryptedBindPass").notNullable();
t.string("bindPassIV").notNullable();
t.string("bindPassTag").notNullable();
t.string("searchBase").notNullable();
t.text("encryptedCACert").notNullable();
t.string("caCertIV").notNullable();
t.string("caCertTag").notNullable();
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.LdapConfig);
if (!(await knex.schema.hasTable(TableName.UserAliases))) {
await knex.schema.createTable(TableName.UserAliases, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.string("username").notNullable();
t.string("aliasType").notNullable();
t.string("externalId").notNullable();
t.specificType("emails", "text[]");
t.uuid("orgId").nullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.UserAliases);
await knex.schema.alterTable(TableName.Users, (t) => {
t.string("username").unique();
t.string("email").nullable().alter();
t.dropUnique(["email"]);
});
await knex(TableName.Users).update("username", knex.ref("email"));
await knex.schema.alterTable(TableName.Users, (t) => {
t.string("username").notNullable().alter();
});
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.LdapConfig);
await knex.schema.dropTableIfExists(TableName.UserAliases);
await knex.schema.alterTable(TableName.Users, (t) => {
t.dropColumn("username");
// t.string("email").notNullable().alter();
});
await dropOnUpdateTrigger(knex, TableName.LdapConfig);
}

@ -0,0 +1,50 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const doesTableExist = await knex.schema.hasTable(TableName.ProjectUserMembershipRole);
if (!doesTableExist) {
await knex.schema.createTable(TableName.ProjectUserMembershipRole, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.uuid("projectMembershipId").notNullable();
t.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
// until role is changed/removed the role should not deleted
t.uuid("customRoleId");
t.foreign("customRoleId").references("id").inTable(TableName.ProjectRoles);
t.boolean("isTemporary").notNullable().defaultTo(false);
t.string("temporaryMode");
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
t.datetime("temporaryAccessStartTime");
t.datetime("temporaryAccessEndTime");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
const projectMemberships = await knex(TableName.ProjectMembership).select(
"id",
"role",
"createdAt",
"updatedAt",
knex.ref("roleId").withSchema(TableName.ProjectMembership).as("customRoleId")
);
if (projectMemberships.length)
await knex.batchInsert(
TableName.ProjectUserMembershipRole,
projectMemberships.map((data) => ({ ...data, projectMembershipId: data.id }))
);
// will be dropped later
// await knex.schema.alterTable(TableName.ProjectMembership, (t) => {
// t.dropColumn("roleId");
// t.dropColumn("role");
// });
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ProjectUserMembershipRole);
await dropOnUpdateTrigger(knex, TableName.ProjectUserMembershipRole);
}

@ -0,0 +1,52 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const doesTableExist = await knex.schema.hasTable(TableName.IdentityProjectMembershipRole);
if (!doesTableExist) {
await knex.schema.createTable(TableName.IdentityProjectMembershipRole, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("role").notNullable();
t.uuid("projectMembershipId").notNullable();
t.foreign("projectMembershipId")
.references("id")
.inTable(TableName.IdentityProjectMembership)
.onDelete("CASCADE");
// until role is changed/removed the role should not deleted
t.uuid("customRoleId");
t.foreign("customRoleId").references("id").inTable(TableName.ProjectRoles);
t.boolean("isTemporary").notNullable().defaultTo(false);
t.string("temporaryMode");
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
t.datetime("temporaryAccessStartTime");
t.datetime("temporaryAccessEndTime");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
const identityMemberships = await knex(TableName.IdentityProjectMembership).select(
"id",
"role",
"createdAt",
"updatedAt",
knex.ref("roleId").withSchema(TableName.IdentityProjectMembership).as("customRoleId")
);
if (identityMemberships.length)
await knex.batchInsert(
TableName.IdentityProjectMembershipRole,
identityMemberships.map((data) => ({ ...data, projectMembershipId: data.id }))
);
// await knex.schema.alterTable(TableName.IdentityProjectMembership, (t) => {
// t.dropColumn("roleId");
// t.dropColumn("role");
// });
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityProjectMembershipRole);
await dropOnUpdateTrigger(knex, TableName.IdentityProjectMembershipRole);
}

@ -0,0 +1,31 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const IdentityProjectMembershipRoleSchema = z.object({
id: z.string().uuid(),
role: z.string(),
projectMembershipId: z.string().uuid(),
customRoleId: z.string().uuid().nullable().optional(),
isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TIdentityProjectMembershipRole = z.infer<typeof IdentityProjectMembershipRoleSchema>;
export type TIdentityProjectMembershipRoleInsert = Omit<
z.input<typeof IdentityProjectMembershipRoleSchema>,
TImmutableDBKeys
>;
export type TIdentityProjectMembershipRoleUpdate = Partial<
Omit<z.input<typeof IdentityProjectMembershipRoleSchema>, TImmutableDBKeys>
>;

@ -8,12 +8,14 @@ export * from "./git-app-org";
export * from "./identities";
export * from "./identity-access-tokens";
export * from "./identity-org-memberships";
export * from "./identity-project-membership-role";
export * from "./identity-project-memberships";
export * from "./identity-ua-client-secrets";
export * from "./identity-universal-auths";
export * from "./incident-contacts";
export * from "./integration-auths";
export * from "./integrations";
export * from "./ldap-configs";
export * from "./models";
export * from "./org-bots";
export * from "./org-memberships";
@ -24,6 +26,7 @@ export * from "./project-environments";
export * from "./project-keys";
export * from "./project-memberships";
export * from "./project-roles";
export * from "./project-user-membership-roles";
export * from "./projects";
export * from "./saml-configs";
export * from "./scim-tokens";
@ -52,6 +55,7 @@ export * from "./service-tokens";
export * from "./super-admin";
export * from "./trusted-ips";
export * from "./user-actions";
export * from "./user-aliases";
export * from "./user-encryption-keys";
export * from "./users";
export * from "./webhooks";

@ -27,7 +27,8 @@ export const IntegrationsSchema = z.object({
envId: z.string().uuid(),
secretPath: z.string().default("/"),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
lastUsed: z.date().nullable().optional()
});
export type TIntegrations = z.infer<typeof IntegrationsSchema>;

@ -0,0 +1,31 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const LdapConfigsSchema = z.object({
id: z.string().uuid(),
orgId: z.string().uuid(),
isActive: z.boolean(),
url: z.string(),
encryptedBindDN: z.string(),
bindDNIV: z.string(),
bindDNTag: z.string(),
encryptedBindPass: z.string(),
bindPassIV: z.string(),
bindPassTag: z.string(),
searchBase: z.string(),
encryptedCACert: z.string(),
caCertIV: z.string(),
caCertTag: z.string(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
export type TLdapConfigsInsert = Omit<z.input<typeof LdapConfigsSchema>, TImmutableDBKeys>;
export type TLdapConfigsUpdate = Partial<Omit<z.input<typeof LdapConfigsSchema>, TImmutableDBKeys>>;

@ -2,6 +2,7 @@ import { z } from "zod";
export enum TableName {
Users = "users",
UserAliases = "user_aliases",
UserEncryptionKey = "user_encryption_keys",
AuthTokens = "auth_tokens",
AuthTokenSession = "auth_token_sessions",
@ -19,6 +20,7 @@ export enum TableName {
Environment = "project_environments",
ProjectMembership = "project_memberships",
ProjectRoles = "project_roles",
ProjectUserMembershipRole = "project_user_membership_roles",
ProjectKeys = "project_keys",
Secret = "secrets",
SecretBlindIndex = "secret_blind_indexes",
@ -40,6 +42,7 @@ export enum TableName {
IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role",
ScimToken = "scim_tokens",
SecretApprovalPolicy = "secret_approval_policies",
SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
@ -50,6 +53,7 @@ export enum TableName {
SecretRotation = "secret_rotations",
SecretRotationOutput = "secret_rotation_outputs",
SamlConfig = "saml_configs",
LdapConfig = "ldap_configs",
AuditLog = "audit_logs",
GitAppInstallSession = "git_app_install_sessions",
GitAppOrg = "git_app_org",

@ -0,0 +1,31 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const ProjectUserMembershipRolesSchema = z.object({
id: z.string().uuid(),
role: z.string(),
projectMembershipId: z.string().uuid(),
customRoleId: z.string().uuid().nullable().optional(),
isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TProjectUserMembershipRoles = z.infer<typeof ProjectUserMembershipRolesSchema>;
export type TProjectUserMembershipRolesInsert = Omit<
z.input<typeof ProjectUserMembershipRolesSchema>,
TImmutableDBKeys
>;
export type TProjectUserMembershipRolesUpdate = Partial<
Omit<z.input<typeof ProjectUserMembershipRolesSchema>, TImmutableDBKeys>
>;

@ -0,0 +1,24 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const UserAliasesSchema = z.object({
id: z.string().uuid(),
userId: z.string().uuid(),
username: z.string(),
aliasType: z.string(),
externalId: z.string(),
emails: z.string().array().nullable().optional(),
orgId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TUserAliases = z.infer<typeof UserAliasesSchema>;
export type TUserAliasesInsert = Omit<z.input<typeof UserAliasesSchema>, TImmutableDBKeys>;
export type TUserAliasesUpdate = Partial<Omit<z.input<typeof UserAliasesSchema>, TImmutableDBKeys>>;

@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const UsersSchema = z.object({
id: z.string().uuid(),
email: z.string(),
email: z.string().nullable().optional(),
authMethods: z.string().array().nullable().optional(),
superAdmin: z.boolean().default(false).nullable().optional(),
firstName: z.string().nullable().optional(),
@ -20,7 +20,8 @@ export const UsersSchema = z.object({
devices: z.unknown().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
isGhost: z.boolean().default(false)
isGhost: z.boolean().default(false),
username: z.string()
});
export type TUsers = z.infer<typeof UsersSchema>;

@ -21,6 +21,7 @@ export let userPublicKey: string | undefined;
export const seedData1 = {
id: "3dafd81d-4388-432b-a4c5-f735616868c1",
username: process.env.TEST_USER_USERNAME || "test@localhost.local",
email: process.env.TEST_USER_EMAIL || "test@localhost.local",
password: process.env.TEST_USER_PASSWORD || "testInfisical@1",
organization: {

@ -9,7 +9,12 @@ export async function seed(knex: Knex): Promise<void> {
await knex(TableName.Users).del();
await knex(TableName.UserEncryptionKey).del();
await knex(TableName.SuperAdmin).del();
await knex(TableName.SuperAdmin).insert([{ initialized: true, allowSignUp: true }]);
await knex(TableName.SuperAdmin).insert([
// eslint-disable-next-line
// @ts-ignore
{ id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true }
]);
// Inserts seed entries
const [user] = await knex(TableName.Users)
.insert([
@ -17,6 +22,7 @@ export async function seed(knex: Knex): Promise<void> {
// eslint-disable-next-line
// @ts-ignore
id: seedData1.id,
username: seedData1.username,
email: seedData1.email,
superAdmin: true,
firstName: "test",

@ -4,7 +4,7 @@ import { Knex } from "knex";
import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { OrgMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
import { ProjectMembershipRole, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data";
export const DEFAULT_PROJECT_ENVS = [
@ -30,10 +30,16 @@ export async function seed(knex: Knex): Promise<void> {
})
.returning("*");
await knex(TableName.ProjectMembership).insert({
projectId: project.id,
role: OrgMembershipRole.Admin,
userId: seedData1.id
const projectMembership = await knex(TableName.ProjectMembership)
.insert({
projectId: project.id,
userId: seedData1.id,
role: ProjectMembershipRole.Admin
})
.returning("*");
await knex(TableName.ProjectUserMembershipRole).insert({
role: ProjectMembershipRole.Admin,
projectMembershipId: projectMembership[0].id
});
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();

@ -75,9 +75,16 @@ export async function seed(knex: Knex): Promise<void> {
}
]);
await knex(TableName.IdentityProjectMembership).insert({
identityId: seedData1.machineIdentity.id,
const identityProjectMembership = await knex(TableName.IdentityProjectMembership)
.insert({
identityId: seedData1.machineIdentity.id,
projectId: seedData1.project.id,
role: ProjectMembershipRole.Admin
})
.returning("*");
await knex(TableName.IdentityProjectMembershipRole).insert({
role: ProjectMembershipRole.Admin,
projectId: seedData1.project.id
projectMembershipId: identityProjectMembership[0].id
});
}

@ -1,3 +1,4 @@
import { registerLdapRouter } from "./ldap-router";
import { registerLicenseRouter } from "./license-router";
import { registerOrgRoleRouter } from "./org-role-router";
import { registerProjectRoleRouter } from "./project-role-router";
@ -35,6 +36,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
});
await server.register(registerSamlRouter, { prefix: "/sso" });
await server.register(registerScimRouter, { prefix: "/scim" });
await server.register(registerLdapRouter, { prefix: "/ldap" });
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" });

@ -0,0 +1,197 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
// All the any rules are disabled because passport typesense with fastify is really poor
import { IncomingMessage } from "node:http";
import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session";
import { FastifyRequest } from "fastify";
import LdapStrategy from "passport-ldapauth";
import { z } from "zod";
import { LdapConfigsSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerLdapRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
const passport = new Authenticator({ key: "ldap", userProperty: "passportUser" });
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
await server.register(passport.initialize());
await server.register(passport.secureSession());
const getLdapPassportOpts = (req: FastifyRequest, done: any) => {
const { organizationSlug } = req.body as {
organizationSlug: string;
};
process.nextTick(async () => {
try {
const { opts, ldapConfig } = await server.services.ldap.bootLdap(organizationSlug);
req.ldapConfig = ldapConfig;
done(null, opts);
} catch (err) {
done(err);
}
});
};
passport.use(
new LdapStrategy(
getLdapPassportOpts as any,
// eslint-disable-next-line
async (req: IncomingMessage, user, cb) => {
try {
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
externalId: user.uidNumber,
username: user.uid,
firstName: user.givenName,
lastName: user.sn,
emails: user.mail ? [user.mail] : [],
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
});
return cb(null, { isUserCompleted, providerAuthToken });
} catch (err) {
logger.error(err);
return cb(err, false);
}
}
)
);
server.route({
url: "/login",
method: "POST",
schema: {
body: z.object({
organizationSlug: z.string().trim()
})
},
preValidation: passport.authenticate("ldapauth", {
session: false
// failureFlash: true,
// failureRedirect: "/login/provider/error"
// this is due to zod type difference
}) as any,
handler: (req, res) => {
let nextUrl;
if (req.passportUser.isUserCompleted) {
nextUrl = `${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`;
} else {
nextUrl = `${appCfg.SITE_URL}/signup/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`;
}
return res.status(200).send({
nextUrl
});
}
});
server.route({
url: "/config",
method: "GET",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
querystring: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
id: z.string(),
organization: z.string(),
isActive: z.boolean(),
url: z.string(),
bindDN: z.string(),
bindPass: z.string(),
searchBase: z.string(),
caCert: z.string()
})
}
},
handler: async (req) => {
const ldap = await server.services.ldap.getLdapCfgWithPermissionCheck({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.query.organizationId,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return ldap;
}
});
server.route({
url: "/config",
method: "POST",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
organizationId: z.string().trim(),
isActive: z.boolean(),
url: z.string().trim(),
bindDN: z.string().trim(),
bindPass: z.string().trim(),
searchBase: z.string().trim(),
caCert: z.string().trim().default("")
}),
response: {
200: LdapConfigsSchema
}
},
handler: async (req) => {
const ldap = await server.services.ldap.createLdapCfg({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.body.organizationId,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return ldap;
}
});
server.route({
url: "/config",
method: "PATCH",
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z
.object({
isActive: z.boolean(),
url: z.string().trim(),
bindDN: z.string().trim(),
bindPass: z.string().trim(),
searchBase: z.string().trim(),
caCert: z.string().trim()
})
.partial()
.merge(z.object({ organizationId: z.string() })),
response: {
200: LdapConfigsSchema
}
},
handler: async (req) => {
const ldap = await server.services.ldap.updateLdapCfg({
actor: req.permission.type,
actorId: req.permission.id,
orgId: req.body.organizationId,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return ldap;
}
});
};

@ -24,6 +24,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
actorAuthMethod: req.permission.authMethod,
billingCycle: req.query.billingCycle
});
return data;
@ -45,6 +46,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return { plan };
@ -66,6 +68,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgPlan({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;
@ -89,6 +93,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
actorAuthMethod: req.permission.authMethod,
success_url: req.body.success_url
});
return data;
@ -110,6 +115,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;
@ -131,6 +137,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;
@ -152,6 +159,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;
@ -173,6 +181,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;
@ -198,6 +207,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId,
name: req.body.name,
email: req.body.email
@ -221,6 +231,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;
@ -246,6 +257,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId,
success_url: req.body.success_url,
cancel_url: req.body.cancel_url
@ -271,6 +283,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.delOrgPmtMethods({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
pmtMethodId: req.params.pmtMethodId
@ -295,6 +308,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.getOrgTaxIds({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId
});
@ -322,6 +336,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.addOrgTaxId({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
type: req.body.type,
@ -348,6 +363,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const data = await server.services.license.delOrgTaxId({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
taxId: req.params.taxId
@ -373,7 +389,8 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId
orgId: req.params.organizationId,
actorAuthMethod: req.permission.authMethod
});
return data;
}
@ -396,6 +413,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId
});
return data;

@ -1,6 +1,7 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -13,7 +14,17 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
organizationId: z.string().trim()
}),
body: z.object({
slug: z.string().trim(),
slug: z
.string()
.min(1)
.trim()
.refine(
(val) => !Object.keys(OrgMembershipRole).includes(val),
"Please choose a different slug, the slug you have entered is reserved"
)
.refine((v) => slugify(v) === v, {
message: "Slug must be a valid"
}),
name: z.string().trim(),
description: z.string().trim().optional(),
permissions: z.any().array()
@ -30,6 +41,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
req.permission.id,
req.params.organizationId,
req.body,
req.permission.authMethod,
req.permission.orgId
);
return { role };
@ -45,7 +57,17 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
roleId: z.string().trim()
}),
body: z.object({
slug: z.string().trim().optional(),
slug: z
.string()
.trim()
.optional()
.refine(
(val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val),
"Please choose a different slug, the slug you have entered is reserved."
)
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
message: "Slug must be a valid"
}),
name: z.string().trim().optional(),
description: z.string().trim().optional(),
permissions: z.any().array()
@ -63,6 +85,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
req.params.organizationId,
req.params.roleId,
req.body,
req.permission.authMethod,
req.permission.orgId
);
return { role };
@ -89,6 +112,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
req.permission.id,
req.params.organizationId,
req.params.roleId,
req.permission.authMethod,
req.permission.orgId
);
return { role };
@ -117,6 +141,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
const roles = await server.services.orgRole.listRoles(
req.permission.id,
req.params.organizationId,
req.permission.authMethod,
req.permission.orgId
);
return { data: { roles } };
@ -142,6 +167,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
const { permissions, membership } = await server.services.orgRole.getUserPermission(
req.permission.id,
req.params.organizationId,
req.permission.authMethod,
req.permission.orgId
);
return { permissions, membership };

@ -31,6 +31,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.id,
req.params.projectId,
req.body,
req.permission.authMethod,
req.permission.orgId
);
return { role };
@ -65,6 +66,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.params.projectId,
req.params.roleId,
req.body,
req.permission.authMethod,
req.permission.orgId
);
return { role };
@ -92,6 +94,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.id,
req.params.projectId,
req.params.roleId,
req.permission.authMethod,
req.permission.orgId
);
return { role };
@ -121,6 +124,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
req.permission.type,
req.permission.id,
req.params.projectId,
req.permission.authMethod,
req.permission.orgId
);
return { data: { roles } };
@ -148,6 +152,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
const { permissions, membership } = await server.services.projectRole.getUserPermission(
req.permission.id,
req.params.projectId,
req.permission.authMethod,
req.permission.orgId
);
return { data: { permissions, membership } };

@ -2,6 +2,7 @@ import { z } from "zod";
import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -19,13 +20,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.workspaceId)
}),
querystring: z.object({
environment: z.string().trim(),
path: z.string().trim().default("/").transform(removeTrailingSlash),
offset: z.coerce.number().default(0),
limit: z.coerce.number().default(20)
environment: z.string().trim().describe(PROJECTS.GET_SNAPSHOTS.environment),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(PROJECTS.GET_SNAPSHOTS.path),
offset: z.coerce.number().default(0).describe(PROJECTS.GET_SNAPSHOTS.offset),
limit: z.coerce.number().default(20).describe(PROJECTS.GET_SNAPSHOTS.limit)
}),
response: {
200: z.object({
@ -37,6 +38,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
handler: async (req) => {
const secretSnapshots = await server.services.snapshot.listSnapshots({
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
@ -68,6 +70,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
const count = await server.services.snapshot.projectSecretSnapshotCount({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
environment: req.query.environment,
@ -89,16 +92,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
workspaceId: z.string().trim()
workspaceId: z.string().trim().describe(AUDIT_LOGS.EXPORT.workspaceId)
}),
querystring: z.object({
eventType: z.nativeEnum(EventType).optional(),
userAgentType: z.nativeEnum(UserAgentType).optional(),
startDate: z.string().datetime().optional(),
endDate: z.string().datetime().optional(),
offset: z.coerce.number().default(0),
limit: z.coerce.number().default(20),
actor: z.string().optional()
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
}),
response: {
200: z.object({
@ -129,6 +132,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
const auditLogs = await server.services.auditLog.listProjectAuditLogs({
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
...req.query,
auditLogActor: req.query.actor,

@ -99,14 +99,14 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
async (req, profile, cb) => {
try {
if (!profile) throw new BadRequestError({ message: "Missing profile" });
const { firstName } = profile;
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
if (!email || !firstName) {
if (!profile.email || !profile.firstName) {
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
}
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
username: profile.nameID ?? email,
email,
firstName: profile.firstName as string,
lastName: profile.lastName as string,
@ -231,6 +231,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
orgId: req.query.organizationId,
type: "org"
});
@ -259,6 +260,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
const saml = await server.services.saml.createSamlCfg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId,
...req.body
@ -290,6 +292,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
const saml = await server.services.saml.updateSamlCfg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId,
...req.body

@ -39,6 +39,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId,
actorAuthMethod: req.permission.authMethod,
description: req.body.description,
ttlDays: req.body.ttlDays
});
@ -65,6 +66,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
const scimTokens = await server.services.scim.listScimTokens({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.query.organizationId
});
@ -92,6 +94,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
scimTokenId: req.params.scimTokenId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
@ -122,7 +125,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
emails: z.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
value: z.string(),
type: z.string().trim()
})
),
@ -168,7 +171,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
emails: z.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
value: z.string(),
type: z.string().trim()
})
),
@ -198,13 +201,15 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
familyName: z.string().trim(),
givenName: z.string().trim()
}),
// emails: z.array( // optional?
// z.object({
// primary: z.boolean(),
// value: z.string().email(),
// type: z.string().trim()
// })
// ),
emails: z
.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
})
)
.optional(),
// displayName: z.string().trim(),
active: z.boolean()
}),
@ -231,8 +236,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => {
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
const user = await req.server.services.scim.createScimUser({
email: req.body.userName,
username: req.body.userName,
email: primaryEmail,
firstName: req.body.name.givenName,
lastName: req.body.name.familyName,
orgId: req.permission.orgId as string

@ -34,6 +34,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId,
...req.body,
@ -72,6 +73,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
secretPolicyId: req.params.sapId
@ -98,6 +100,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretPolicyId: req.params.sapId
});
@ -123,6 +126,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const approvals = await server.services.secretApprovalPolicy.getSecretApprovalPolicyByProjectId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId
});
@ -150,6 +154,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
const policy = await server.services.secretApprovalPolicy.getSecretApprovalPolicyOfFolder({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId,
...req.query

@ -52,6 +52,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approvals = await server.services.secretApprovalRequest.getSecretApprovals({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query,
projectId: req.query.workspaceId
@ -81,6 +82,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approvals = await server.services.secretApprovalRequest.requestCount({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId
});
@ -106,6 +108,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const { approval } = await server.services.secretApprovalRequest.mergeSecretApprovalRequest({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
approvalId: req.params.id
});
@ -134,6 +137,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const review = await server.services.secretApprovalRequest.reviewApproval({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
approvalId: req.params.id,
status: req.body.status
@ -163,6 +167,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approval = await server.services.secretApprovalRequest.updateApprovalStatus({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
approvalId: req.params.id,
status: req.body.status
@ -271,6 +276,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.id
});

@ -30,6 +30,7 @@ export const registerSecretRotationProviderRouter = async (server: FastifyZodPro
const providers = await server.services.secretRotation.getProviderTemplates({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
});

@ -39,6 +39,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
handler: async (req) => {
const secretRotation = await server.services.secretRotation.createRotation({
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
...req.body,
@ -74,6 +75,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotation = await server.services.secretRotation.restartById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
rotationId: req.body.id
});
@ -125,6 +127,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotations = await server.services.secretRotation.getByProjectId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.workspaceId
});
@ -158,6 +161,7 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
const secretRotation = await server.services.secretRotation.deleteById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
rotationId: req.params.id
});

@ -22,6 +22,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const session = await server.services.secretScanning.createInstallationSession({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId
});
@ -46,6 +47,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const { installatedApp } = await server.services.secretScanning.linkInstallationToOrg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
@ -67,6 +69,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const appInstallationCompleted = await server.services.secretScanning.getOrgInstallationStatus({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId
});
@ -88,6 +91,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const { risks } = await server.services.secretScanning.getRisksByOrg({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId
});
@ -110,6 +114,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
const { risk } = await server.services.secretScanning.updateRiskStatus({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
orgId: req.params.organizationId,
riskId: req.params.riskId,

@ -27,6 +27,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
const secretVersions = await server.services.secret.getSecretVersions({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
limit: req.query.limit,
offset: req.query.offset,

@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -46,6 +47,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
const secretSnapshot = await server.services.snapshot.getSnapshotData({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.secretSnapshotId
});
@ -65,7 +67,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretSnapshotId: z.string().trim()
secretSnapshotId: z.string().trim().describe(PROJECTS.ROLLBACK_TO_SNAPSHOT.secretSnapshotId)
}),
response: {
200: z.object({
@ -78,6 +80,7 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
const secretSnapshot = await server.services.snapshot.rollbackSnapshot({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.secretSnapshotId
});

@ -22,6 +22,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const trustedIps = await server.services.trustedIp.listIpsByProjectId({
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
actor: req.permission.type,
actorId: req.permission.id,
@ -52,6 +53,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { trustedIp, project } = await server.services.trustedIp.addProjectIp({
actorAuthMethod: req.permission.authMethod,
projectId: req.params.workspaceId,
actor: req.permission.type,
actorId: req.permission.id,
@ -99,6 +101,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
trustedIpId: req.params.trustedIpId,
...req.body
@ -140,6 +143,7 @@ export const registerTrustedIpRouter = async (server: FastifyZodProvider) => {
projectId: req.params.workspaceId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
trustedIpId: req.params.trustedIpId
});

@ -31,10 +31,17 @@ export const auditLogServiceFactory = ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
projectId,
auditLogActor
}: TListProjectAuditLogDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
const auditLogs = await auditLogDAL.find({
startDate,

@ -92,7 +92,8 @@ export enum EventType {
interface UserActorMetadata {
userId: string;
email: string;
email?: string | null;
username: string;
}
interface ServiceActorMetadata {

@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TLdapConfigDALFactory = ReturnType<typeof ldapConfigDALFactory>;
export const ldapConfigDALFactory = (db: TDbClient) => {
const ldapCfgOrm = ormify(db, TableName.LdapConfig);
return { ...ldapCfgOrm };
};

@ -0,0 +1,437 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import {
decryptSymmetric,
encryptSymmetric,
generateAsymmetricKeyPair,
generateSymmetricKey,
infisicalSymmetricDecrypt,
infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { TOrgPermission } from "@app/lib/types";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TLdapConfigDALFactory } from "./ldap-config-dal";
import { TCreateLdapCfgDTO, TLdapLoginDTO, TUpdateLdapCfgDTO } from "./ldap-config-types";
type TLdapConfigServiceFactoryDep = {
ldapConfigDAL: TLdapConfigDALFactory;
orgDAL: Pick<
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>;
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">;
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>;
export const ldapConfigServiceFactory = ({
ldapConfigDAL,
orgDAL,
orgBotDAL,
userDAL,
userAliasDAL,
permissionService,
licenseService
}: TLdapConfigServiceFactoryDep) => {
const createLdapCfg = async ({
actor,
actorId,
orgId,
actorOrgId,
actorAuthMethod,
isActive,
url,
bindDN,
bindPass,
searchBase,
caCert
}: TCreateLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId);
if (!plan.ldap)
throw new BadRequestError({
message:
"Failed to create LDAP configuration due to plan restriction. Upgrade plan to create LDAP configuration."
});
const orgBot = await orgBotDAL.transaction(async (tx) => {
const doc = await orgBotDAL.findOne({ orgId }, tx);
if (doc) return doc;
const { privateKey, publicKey } = generateAsymmetricKeyPair();
const key = generateSymmetricKey();
const {
ciphertext: encryptedPrivateKey,
iv: privateKeyIV,
tag: privateKeyTag,
encoding: privateKeyKeyEncoding,
algorithm: privateKeyAlgorithm
} = infisicalSymmetricEncypt(privateKey);
const {
ciphertext: encryptedSymmetricKey,
iv: symmetricKeyIV,
tag: symmetricKeyTag,
encoding: symmetricKeyKeyEncoding,
algorithm: symmetricKeyAlgorithm
} = infisicalSymmetricEncypt(key);
return orgBotDAL.create(
{
name: "Infisical org bot",
publicKey,
privateKeyIV,
encryptedPrivateKey,
symmetricKeyIV,
symmetricKeyTag,
encryptedSymmetricKey,
symmetricKeyAlgorithm,
orgId,
privateKeyTag,
privateKeyAlgorithm,
privateKeyKeyEncoding,
symmetricKeyKeyEncoding
},
tx
);
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
const ldapConfig = await ldapConfigDAL.create({
orgId,
isActive,
url,
encryptedBindDN,
bindDNIV,
bindDNTag,
encryptedBindPass,
bindPassIV,
bindPassTag,
searchBase,
encryptedCACert,
caCertIV,
caCertTag
});
return ldapConfig;
};
const updateLdapCfg = async ({
actor,
actorId,
orgId,
actorOrgId,
isActive,
actorAuthMethod,
url,
bindDN,
bindPass,
searchBase,
caCert
}: TUpdateLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId);
if (!plan.ldap)
throw new BadRequestError({
message:
"Failed to update LDAP configuration due to plan restriction. Upgrade plan to update LDAP configuration."
});
const updateQuery: TLdapConfigsUpdate = {
isActive,
url,
searchBase
};
const orgBot = await orgBotDAL.findOne({ orgId });
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
if (bindDN !== undefined) {
const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key);
updateQuery.encryptedBindDN = encryptedBindDN;
updateQuery.bindDNIV = bindDNIV;
updateQuery.bindDNTag = bindDNTag;
}
if (bindPass !== undefined) {
const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key);
updateQuery.encryptedBindPass = encryptedBindPass;
updateQuery.bindPassIV = bindPassIV;
updateQuery.bindPassTag = bindPassTag;
}
if (caCert !== undefined) {
const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key);
updateQuery.encryptedCACert = encryptedCACert;
updateQuery.caCertIV = caCertIV;
updateQuery.caCertTag = caCertTag;
}
const [ldapConfig] = await ldapConfigDAL.update({ orgId }, updateQuery);
return ldapConfig;
};
const getLdapCfg = async (filter: { orgId: string; isActive?: boolean }) => {
const ldapConfig = await ldapConfigDAL.findOne(filter);
if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" });
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
tag: orgBot.symmetricKeyTag,
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
const {
encryptedBindDN,
bindDNIV,
bindDNTag,
encryptedBindPass,
bindPassIV,
bindPassTag,
encryptedCACert,
caCertIV,
caCertTag
} = ldapConfig;
let bindDN = "";
if (encryptedBindDN && bindDNIV && bindDNTag) {
bindDN = decryptSymmetric({
ciphertext: encryptedBindDN,
key,
tag: bindDNTag,
iv: bindDNIV
});
}
let bindPass = "";
if (encryptedBindPass && bindPassIV && bindPassTag) {
bindPass = decryptSymmetric({
ciphertext: encryptedBindPass,
key,
tag: bindPassTag,
iv: bindPassIV
});
}
let caCert = "";
if (encryptedCACert && caCertIV && caCertTag) {
caCert = decryptSymmetric({
ciphertext: encryptedCACert,
key,
tag: caCertTag,
iv: caCertIV
});
}
return {
id: ldapConfig.id,
organization: ldapConfig.orgId,
isActive: ldapConfig.isActive,
url: ldapConfig.url,
bindDN,
bindPass,
searchBase: ldapConfig.searchBase,
caCert
};
};
const getLdapCfgWithPermissionCheck = async ({
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
}: TOrgPermission) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
return getLdapCfg({
orgId
});
};
const bootLdap = async (organizationSlug: string) => {
const organization = await orgDAL.findOne({ slug: organizationSlug });
if (!organization) throw new BadRequestError({ message: "Org not found" });
const ldapConfig = await getLdapCfg({
orgId: organization.id,
isActive: true
});
const opts = {
server: {
url: ldapConfig.url,
bindDN: ldapConfig.bindDN,
bindCredentials: ldapConfig.bindPass,
searchBase: ldapConfig.searchBase,
searchFilter: "(uid={{username}})",
searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
...(ldapConfig.caCert !== ""
? {
tlsOptions: {
ca: [ldapConfig.caCert]
}
}
: {})
},
passReqToCallback: true
};
return { opts, ldapConfig };
};
const ldapLogin = async ({ externalId, username, firstName, lastName, emails, orgId, relayState }: TLdapLoginDTO) => {
const appCfg = getConfig();
let userAlias = await userAliasDAL.findOne({
externalId,
orgId,
aliasType: AuthMethod.LDAP
});
const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new BadRequestError({ message: "Org not found" });
if (userAlias) {
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership({ userId: userAlias.userId }, { tx });
if (!orgMembership) {
await orgDAL.createMembership(
{
userId: userAlias.userId,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Accepted
},
tx
);
} else if (orgMembership.status === OrgMembershipStatus.Invited) {
await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
});
} else {
userAlias = await userDAL.transaction(async (tx) => {
const uniqueUsername = await normalizeUsername(username, userDAL);
const newUser = await userDAL.create(
{
username: uniqueUsername,
email: emails[0],
firstName,
lastName,
authMethods: [AuthMethod.LDAP],
isGhost: false
},
tx
);
const newUserAlias = await userAliasDAL.create(
{
userId: newUser.id,
username,
aliasType: AuthMethod.LDAP,
externalId,
emails,
orgId
},
tx
);
await orgDAL.createMembership(
{
userId: newUser.id,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
},
tx
);
return newUserAlias;
});
}
const user = await userDAL.findOne({ id: userAlias.userId });
const isUserCompleted = Boolean(user.isAccepted);
const providerAuthToken = jwt.sign(
{
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
username: user.username,
firstName,
lastName,
organizationName: organization.name,
organizationId: organization.id,
authMethod: AuthMethod.LDAP,
isUserCompleted,
...(relayState
? {
callbackPort: (JSON.parse(relayState) as { callbackPort: string }).callbackPort
}
: {})
},
appCfg.AUTH_SECRET,
{
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
}
);
return { isUserCompleted, providerAuthToken };
};
return {
createLdapCfg,
updateLdapCfg,
getLdapCfgWithPermissionCheck,
getLdapCfg,
// getLdapPassportOpts,
ldapLogin,
bootLdap
};
};

@ -0,0 +1,30 @@
import { TOrgPermission } from "@app/lib/types";
export type TCreateLdapCfgDTO = {
isActive: boolean;
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
caCert: string;
} & TOrgPermission;
export type TUpdateLdapCfgDTO = Partial<{
isActive: boolean;
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
caCert: string;
}> &
TOrgPermission;
export type TLdapLoginDTO = {
externalId: string;
username: string;
firstName: string;
lastName: string;
emails: string[];
orgId: string;
relayState?: string;
};

@ -18,6 +18,8 @@ export const getDefaultOnPremFeatures = () => {
auditLogs: false,
auditLogsRetentionDays: 0,
samlSSO: false,
scim: false,
ldap: false,
status: null,
trial_end: null,
has_used_trial: true,

@ -25,6 +25,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
auditLogsRetentionDays: 0,
samlSSO: false,
scim: false,
ldap: false,
status: null,
trial_end: null,
has_used_trial: true,

@ -8,6 +8,7 @@ import { ForbiddenError } from "@casl/ability";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { verifyOfflineLicense } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TOrgDALFactory } from "@app/services/org/org-dal";
@ -26,6 +27,7 @@ import {
TFeatureSet,
TGetOrgBillInfoDTO,
TGetOrgTaxIdDTO,
TOfflineLicenseContents,
TOrgInvoiceDTO,
TOrgLicensesDTO,
TOrgPlanDTO,
@ -96,6 +98,36 @@ export const licenseServiceFactory = ({
}
return;
}
if (appCfg.LICENSE_KEY_OFFLINE) {
let isValidOfflineLicense = true;
const contents: TOfflineLicenseContents = JSON.parse(
Buffer.from(appCfg.LICENSE_KEY_OFFLINE, "base64").toString("utf8")
);
const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature);
if (!isVerified) {
isValidOfflineLicense = false;
logger.warn(`Infisical EE offline license verification failed`);
}
if (contents.license.terminatesAt) {
const terminationDate = new Date(contents.license.terminatesAt);
if (terminationDate < new Date()) {
isValidOfflineLicense = false;
logger.warn(`Infisical EE offline license has expired`);
}
}
if (isValidOfflineLicense) {
onPremFeatures = contents.license.features;
instanceType = InstanceType.EnterpriseOnPrem;
logger.info(`Instance type: ${InstanceType.EnterpriseOnPrem}`);
isValidLicense = true;
return;
}
}
// this means this is self hosted oss version
// else it would reach catch statement
isValidLicense = true;
@ -147,14 +179,14 @@ export const licenseServiceFactory = ({
}
};
const generateOrgCustomerId = async (orgName: string, email: string) => {
const generateOrgCustomerId = async (orgName: string, email?: string | null) => {
if (instanceType === InstanceType.Cloud) {
const {
data: { customerId }
} = await licenseServerCloudApi.request.post<{ customerId: string }>(
"/api/license-server/v1/customers",
{
email,
email: email ?? "",
name: orgName
},
{ timeout: 5000, signal: AbortSignal.timeout(5000) }
@ -192,9 +224,10 @@ export const licenseServiceFactory = ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
billingCycle
}: TOrgPlansTableDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
@ -202,15 +235,22 @@ export const licenseServiceFactory = ({
return data;
};
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, projectId }: TOrgPlanDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const plan = await getPlan(orgId, projectId);
return plan;
};
const startOrgTrial = async ({ orgId, actorId, actor, actorOrgId, success_url }: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const startOrgTrial = async ({
orgId,
actorId,
actor,
actorOrgId,
actorAuthMethod,
success_url
}: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
@ -231,8 +271,14 @@ export const licenseServiceFactory = ({
return { url };
};
const createOrganizationPortalSession = async ({ orgId, actorId, actor, actorOrgId }: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const createOrganizationPortalSession = async ({
orgId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);
@ -278,8 +324,8 @@ export const licenseServiceFactory = ({
return { url };
};
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -295,8 +341,8 @@ export const licenseServiceFactory = ({
};
// returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -311,8 +357,8 @@ export const licenseServiceFactory = ({
return data;
};
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -332,11 +378,12 @@ export const licenseServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
orgId,
name,
email
}: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -355,8 +402,8 @@ export const licenseServiceFactory = ({
return data;
};
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorOrgId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -378,11 +425,12 @@ export const licenseServiceFactory = ({
orgId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
success_url,
cancel_url
}: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -403,8 +451,15 @@ export const licenseServiceFactory = ({
return { url };
};
const delOrgPmtMethods = async ({ actorId, actor, actorOrgId, orgId, pmtMethodId }: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const delOrgPmtMethods = async ({
actorId,
actor,
actorAuthMethod,
actorOrgId,
orgId,
pmtMethodId
}: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -420,8 +475,8 @@ export const licenseServiceFactory = ({
return data;
};
const getOrgTaxIds = async ({ orgId, actor, actorId, actorOrgId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -438,8 +493,8 @@ export const licenseServiceFactory = ({
return taxIds;
};
const addOrgTaxId = async ({ actorId, actor, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -459,8 +514,8 @@ export const licenseServiceFactory = ({
return data;
};
const delOrgTaxId = async ({ orgId, actor, actorId, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -476,8 +531,8 @@ export const licenseServiceFactory = ({
return data;
};
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);
@ -493,8 +548,8 @@ export const licenseServiceFactory = ({
return invoices;
};
const getOrgLicenses = async ({ orgId, actor, actorId, actorOrgId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId);

@ -6,6 +6,21 @@ export enum InstanceType {
Cloud = "cloud"
}
export type TOfflineLicenseContents = {
license: TOfflineLicense;
signature: string;
};
export type TOfflineLicense = {
issuedTo: string;
licenseId: string;
customerId: string | null;
issuedAt: string;
expiresAt: string | null;
terminatesAt: string | null;
features: TFeatureSet;
};
export type TFeatureSet = {
_id: null;
slug: null;
@ -26,6 +41,7 @@ export type TFeatureSet = {
auditLogsRetentionDays: 0;
samlSSO: false;
scim: false;
ldap: false;
status: null;
trial_end: null;
has_used_trial: true;

@ -17,6 +17,7 @@ export enum OrgPermissionSubjects {
IncidentAccount = "incident-contact",
Sso = "sso",
Scim = "scim",
Ldap = "ldap",
Billing = "billing",
SecretScanning = "secret-scanning",
Identity = "identity"
@ -31,6 +32,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
@ -76,6 +78,11 @@ const buildAdminPermission = () => {
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
can(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Billing);
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Billing);

@ -1,7 +1,9 @@
import { z } from "zod";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { IdentityProjectMembershipRoleSchema, ProjectUserMembershipRolesSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { selectAllTableCols } from "@app/lib/knex";
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
@ -43,21 +45,72 @@ export const permissionDALFactory = (db: TDbClient) => {
const getProjectPermission = async (userId: string, projectId: string) => {
try {
const membership = await db(TableName.ProjectMembership)
.leftJoin(TableName.ProjectRoles, `${TableName.ProjectMembership}.roleId`, `${TableName.ProjectRoles}.id`)
const docs = await db(TableName.ProjectMembership)
.join(
TableName.ProjectUserMembershipRole,
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
`${TableName.ProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.ProjectUserMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.where("userId", userId)
.where(`${TableName.ProjectMembership}.projectId`, projectId)
.select(selectAllTableCols(TableName.ProjectMembership))
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
.select(
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
// TODO(roll-forward-migration): remove this field when we drop this in next migration after a week
db.ref("role").withSchema(TableName.ProjectMembership).as("oldRoleField"),
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project)
db.ref("orgId").withSchema(TableName.Project),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
)
.select("permissions")
.first();
.select("permissions");
return membership;
const permission = sqlNestRelationships({
data: docs,
key: "membershipId",
parentMapper: ({
orgId,
orgAuthEnforced,
membershipId,
membershipCreatedAt,
membershipUpdatedAt,
oldRoleField
}) => ({
orgId,
orgAuthEnforced,
userId,
role: oldRoleField,
id: membershipId,
projectId,
createdAt: membershipCreatedAt,
updatedAt: membershipUpdatedAt
}),
childrenMapper: [
{
key: "id",
label: "roles" as const,
mapper: (data) =>
ProjectUserMembershipRolesSchema.extend({
permissions: z.unknown(),
customRoleSlug: z.string().optional().nullable()
}).parse(data)
}
]
});
// when introducting cron mode change it here
const activeRoles = permission?.[0]?.roles.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectPermission" });
}
@ -65,18 +118,69 @@ export const permissionDALFactory = (db: TDbClient) => {
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
try {
const membership = await db(TableName.IdentityProjectMembership)
const docs = await db(TableName.IdentityProjectMembership)
.join(
TableName.IdentityProjectMembershipRole,
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
`${TableName.IdentityProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.IdentityProjectMembership}.roleId`,
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.join(
// Join the Project table to later select orgId
TableName.Project,
`${TableName.IdentityProjectMembership}.projectId`,
`${TableName.Project}.id`
)
.where("identityId", identityId)
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
.select(selectAllTableCols(TableName.IdentityProjectMembership))
.select("permissions")
.first();
return membership;
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
.select(
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
db.ref("role").withSchema(TableName.IdentityProjectMembership).as("oldRoleField"),
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
)
.select("permissions");
const permission = sqlNestRelationships({
data: docs,
key: "membershipId",
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, oldRoleField, orgId }) => ({
id: membershipId,
identityId,
projectId,
role: oldRoleField,
createdAt: membershipCreatedAt,
updatedAt: membershipUpdatedAt,
orgId,
// just a prefilled value
orgAuthEnforced: false
}),
childrenMapper: [
{
key: "id",
label: "roles" as const,
mapper: (data) =>
IdentityProjectMembershipRoleSchema.extend({
permissions: z.unknown(),
customRoleSlug: z.string().optional().nullable()
}).parse(data)
}
]
});
// when introducting cron mode change it here
const activeRoles = permission?.[0]?.roles.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return permission?.[0] ? { ...permission[0], roles: activeRoles } : undefined;
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectIdentityPermission" });
}

@ -0,0 +1,23 @@
import { TOrganizations } from "@app/db/schemas";
import { UnauthorizedError } from "@app/lib/errors";
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
if (!actorAuthMethod) return false;
return [AuthMethod.AZURE_SAML, AuthMethod.OKTA_SAML, AuthMethod.JUMPCLOUD_SAML, AuthMethod.GOOGLE_SAML].includes(
actorAuthMethod
);
}
function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
if (actorAuthMethod === undefined) {
throw new UnauthorizedError({ name: "No auth method defined" });
}
if (isSamlEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod)) {
throw new UnauthorizedError({ name: "Cannot access org-scoped resource" });
}
}
export { isAuthMethodSaml, validateOrgSAML };

@ -11,13 +11,16 @@ import {
} from "@app/db/schemas";
import { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { TServiceTokenDALFactory } from "@app/services/service-token/service-token-dal";
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal";
import { validateOrgSAML } from "./permission-fns";
import { TBuildProjectPermissionDTO } from "./permission-types";
import {
buildServiceTokenProjectPermission,
projectAdminPermissions,
@ -31,6 +34,7 @@ type TPermissionServiceFactoryDep = {
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
projectRoleDAL: Pick<TProjectRoleDALFactory, "findOne">;
serviceTokenDAL: Pick<TServiceTokenDALFactory, "findById">;
projectDAL: Pick<TProjectDALFactory, "findById">;
permissionDAL: TPermissionDALFactory;
};
@ -40,7 +44,8 @@ export const permissionServiceFactory = ({
permissionDAL,
orgRoleDAL,
projectRoleDAL,
serviceTokenDAL
serviceTokenDAL,
projectDAL
}: TPermissionServiceFactoryDep) => {
const buildOrgPermission = (role: string, permission?: unknown) => {
switch (role) {
@ -64,45 +69,63 @@ export const permissionServiceFactory = ({
}
};
const buildProjectPermission = (role: string, permission?: unknown) => {
switch (role) {
case ProjectMembershipRole.Admin:
return projectAdminPermissions;
case ProjectMembershipRole.Member:
return projectMemberPermissions;
case ProjectMembershipRole.Viewer:
return projectViewerPermission;
case ProjectMembershipRole.NoAccess:
return projectNoAccessPermissions;
case ProjectMembershipRole.Custom:
return createMongoAbility<ProjectPermissionSet>(
unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
permission as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
),
{
conditionsMatcher
const buildProjectPermission = (projectUserRoles: TBuildProjectPermissionDTO) => {
const rules = projectUserRoles
.map(({ role, permissions }) => {
switch (role) {
case ProjectMembershipRole.Admin:
return projectAdminPermissions;
case ProjectMembershipRole.Member:
return projectMemberPermissions;
case ProjectMembershipRole.Viewer:
return projectViewerPermission;
case ProjectMembershipRole.NoAccess:
return projectNoAccessPermissions;
case ProjectMembershipRole.Custom: {
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
);
}
);
default:
throw new BadRequestError({
name: "ProjectRoleInvalid",
message: "Project role not found"
});
}
default:
throw new BadRequestError({
name: "ProjectRoleInvalid",
message: "Project role not found"
});
}
})
.reduce((curr, prev) => prev.concat(curr), []);
return createMongoAbility<ProjectPermissionSet>(rules, {
conditionsMatcher
});
};
/*
* Get user permission in an organization
* */
const getUserOrgPermission = async (userId: string, orgId: string, userOrgId?: string) => {
*/
const getUserOrgPermission = async (
userId: string,
orgId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
) => {
const membership = await permissionDAL.getOrgPermission(userId, orgId);
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
throw new BadRequestError({ name: "Custom permission not found" });
}
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
// If the org ID is API_KEY, the request is being made with an API Key.
// Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization.
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
throw new UnauthorizedError({ name: "You are not logged into this organization" });
}
validateOrgSAML(authMethod, membership.orgAuthEnforced);
return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
};
@ -115,10 +138,16 @@ export const permissionServiceFactory = ({
return { permission: buildOrgPermission(membership.role, membership.permissions), membership };
};
const getOrgPermission = async (type: ActorType, id: string, orgId: string, actorOrgId?: string) => {
const getOrgPermission = async (
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => {
switch (type) {
case ActorType.USER:
return getUserOrgPermission(id, orgId, actorOrgId);
return getUserOrgPermission(id, orgId, authMethod, actorOrgId);
case ActorType.IDENTITY:
return getIdentityOrgPermission(id, orgId);
default:
@ -145,44 +174,94 @@ export const permissionServiceFactory = ({
};
// user permission for a project in an organization
const getUserProjectPermission = async (userId: string, projectId: string, userOrgId?: string) => {
const getUserProjectPermission = async (
userId: string,
projectId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
): Promise<TProjectPermissionRT<ActorType.USER>> => {
const membership = await permissionDAL.getProjectPermission(userId, projectId);
if (!membership) throw new UnauthorizedError({ name: "User not in project" });
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
if (membership.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)) {
throw new BadRequestError({ name: "Custom permission not found" });
}
if (membership.orgAuthEnforced && membership.orgId !== userOrgId) {
throw new BadRequestError({ name: "Cannot access org-scoped resource" });
// If the org ID is API_KEY, the request is being made with an API Key.
// Since we can't scope API keys to an organization, we'll need to do an arbitrary check to see if the user is a member of the organization.
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
throw new UnauthorizedError({ name: "You are not logged into this organization" });
}
validateOrgSAML(authMethod, membership.orgAuthEnforced);
return {
permission: buildProjectPermission(membership.role, membership.permissions),
membership
permission: buildProjectPermission(membership.roles),
membership,
hasRole: (role: string) =>
membership.roles.findIndex(({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug) !== -1
};
};
const getIdentityProjectPermission = async (identityId: string, projectId: string) => {
const membership = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!membership) throw new UnauthorizedError({ name: "Identity not in project" });
if (membership.role === ProjectMembershipRole.Custom && !membership.permissions) {
const getIdentityProjectPermission = async (
identityId: string,
projectId: string,
identityOrgId: string | undefined
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
if (
identityProjectPermission.roles.some(
({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions
)
) {
throw new BadRequestError({ name: "Custom permission not found" });
}
if (identityProjectPermission.orgId !== identityOrgId) {
throw new UnauthorizedError({ name: "You are not a member of this organization" });
}
return {
permission: buildProjectPermission(membership.role, membership.permissions),
membership
permission: buildProjectPermission(identityProjectPermission.roles),
membership: identityProjectPermission,
hasRole: (role: string) =>
identityProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
) !== -1
};
};
const getServiceTokenProjectPermission = async (serviceTokenId: string, projectId: string) => {
const getServiceTokenProjectPermission = async (
serviceTokenId: string,
projectId: string,
actorOrgId: string | undefined
) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new BadRequestError({ message: "Service token not found" });
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" });
if (serviceTokenProject.orgId !== actorOrgId) {
throw new UnauthorizedError({ message: "Service token not a part of this organization" });
}
if (serviceToken.projectId !== projectId)
throw new UnauthorizedError({
message: "Failed to find service authorization for given project"
});
if (serviceTokenProject.orgId !== actorOrgId)
throw new UnauthorizedError({
message: "Failed to find service authorization for given project"
});
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
@ -191,29 +270,35 @@ export const permissionServiceFactory = ({
};
type TProjectPermissionRT<T extends ActorType> = T extends ActorType.SERVICE
? { permission: MongoAbility<ProjectPermissionSet, MongoQuery>; membership: undefined }
? {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: undefined;
hasRole: (arg: string) => boolean;
} // service token doesn't have both membership and roles
: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: (T extends ActorType.USER ? TProjectMemberships : TIdentityProjectMemberships) & {
orgAuthEnforced: boolean;
orgAuthEnforced: boolean | null | undefined;
orgId: string;
permissions?: unknown;
roles: Array<{ role: string }>;
};
hasRole: (role: string) => boolean;
};
const getProjectPermission = async <T extends ActorType>(
type: T,
id: string,
projectId: string,
actorOrgId?: string
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
): Promise<TProjectPermissionRT<T>> => {
switch (type) {
case ActorType.USER:
return getUserProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE:
return getServiceTokenProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY:
return getIdentityProjectPermission(id, projectId) as Promise<TProjectPermissionRT<T>>;
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
default:
throw new UnauthorizedError({
message: "Permission not defined",
@ -228,11 +313,13 @@ export const permissionServiceFactory = ({
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
if (!projectRole) throw new BadRequestError({ message: "Role not found" });
return {
permission: buildProjectPermission(ProjectMembershipRole.Custom, projectRole.permissions),
permission: buildProjectPermission([
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
]),
role: projectRole
};
}
return { permission: buildProjectPermission(role, []) };
return { permission: buildProjectPermission([{ role, permissions: [] }]) };
};
return {

@ -0,0 +1,4 @@
export type TBuildProjectPermissionDTO = {
permissions?: unknown;
role: string;
}[];

@ -56,8 +56,8 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
const buildAdminPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
const buildAdminPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
@ -135,13 +135,13 @@ const buildAdminPermission = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
return build({ conditionsMatcher });
return rules;
};
export const projectAdminPermissions = buildAdminPermission();
export const projectAdminPermissions = buildAdminPermissionRules();
const buildMemberPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
const buildMemberPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
@ -196,13 +196,13 @@ const buildMemberPermission = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
return build({ conditionsMatcher });
return rules;
};
export const projectMemberPermissions = buildMemberPermission();
export const projectMemberPermissions = buildMemberPermissionRules();
const buildViewerPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
const buildViewerPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
@ -220,14 +220,14 @@ const buildViewerPermission = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
return build({ conditionsMatcher });
return rules;
};
export const projectViewerPermission = buildViewerPermission();
export const projectViewerPermission = buildViewerPermissionRules();
const buildNoAccessProjectPermission = () => {
const { build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
return build({ conditionsMatcher });
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
return rules;
};
export const buildServiceTokenProjectPermission = (

@ -5,6 +5,7 @@ import {
OrgMembershipRole,
OrgMembershipStatus,
SecretKeyEncoding,
TableName,
TSamlConfigs,
TSamlConfigsUpdate
} from "@app/db/schemas";
@ -31,7 +32,7 @@ import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } f
type TSamlConfigServiceFactoryDep = {
samlConfigDAL: TSamlConfigDALFactory;
userDAL: Pick<TUserDALFactory, "create" | "findUserByEmail" | "transaction" | "updateById">;
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">;
orgDAL: Pick<
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
@ -54,6 +55,7 @@ export const samlConfigServiceFactory = ({
const createSamlCfg = async ({
cert,
actor,
actorAuthMethod,
actorOrgId,
orgId,
issuer,
@ -62,14 +64,14 @@ export const samlConfigServiceFactory = ({
entryPoint,
authProvider
}: TCreateSamlCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId);
if (!plan.samlSSO)
throw new BadRequestError({
message:
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
"Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to create SSO configuration."
});
const orgBot = await orgBotDAL.transaction(async (tx) => {
@ -122,7 +124,6 @@ export const samlConfigServiceFactory = ({
const { ciphertext: encryptedEntryPoint, iv: entryPointIV, tag: entryPointTag } = encryptSymmetric(entryPoint, key);
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
const samlConfig = await samlConfigDAL.create({
orgId,
@ -146,6 +147,7 @@ export const samlConfigServiceFactory = ({
orgId,
actor,
actorOrgId,
actorAuthMethod,
cert,
actorId,
issuer,
@ -153,7 +155,7 @@ export const samlConfigServiceFactory = ({
entryPoint,
authProvider
}: TUpdateSamlCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId);
if (!plan.samlSSO)
@ -172,7 +174,7 @@ export const samlConfigServiceFactory = ({
keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding
});
if (entryPoint) {
if (entryPoint !== undefined) {
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
@ -182,18 +184,19 @@ export const samlConfigServiceFactory = ({
updateQuery.entryPointIV = entryPointIV;
updateQuery.entryPointTag = entryPointTag;
}
if (issuer) {
if (issuer !== undefined) {
const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key);
updateQuery.encryptedIssuer = encryptedIssuer;
updateQuery.issuerIV = issuerIV;
updateQuery.issuerTag = issuerTag;
}
if (cert) {
if (cert !== undefined) {
const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key);
updateQuery.encryptedCert = encryptedCert;
updateQuery.certIV = certIV;
updateQuery.certTag = certTag;
}
const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery);
await orgDAL.updateById(orgId, { authEnforced: false, scimEnabled: false });
@ -237,6 +240,7 @@ export const samlConfigServiceFactory = ({
dto.actor,
dto.actorId,
ssoConfig.orgId,
dto.actorAuthMethod,
dto.actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
@ -300,16 +304,30 @@ export const samlConfigServiceFactory = ({
};
};
const samlLogin = async ({ firstName, email, lastName, authProvider, orgId, relayState }: TSamlLoginDTO) => {
const samlLogin = async ({
username,
email,
firstName,
lastName,
authProvider,
orgId,
relayState
}: TSamlLoginDTO) => {
const appCfg = getConfig();
let user = await userDAL.findUserByEmail(email);
let user = await userDAL.findOne({ username });
const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new BadRequestError({ message: "Org not found" });
if (user) {
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx });
const [orgMembership] = await orgDAL.findMembership(
{
userId: user.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
},
{ tx }
);
if (!orgMembership) {
await orgDAL.createMembership(
{
@ -335,6 +353,7 @@ export const samlConfigServiceFactory = ({
user = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create(
{
username,
email,
firstName,
lastName,
@ -357,7 +376,7 @@ export const samlConfigServiceFactory = ({
{
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
email: user.email,
username: user.username,
firstName,
lastName,
organizationName: organization.name,

@ -1,5 +1,5 @@
import { TOrgPermission } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
export enum SamlProviders {
OKTA_SAML = "okta-saml",
@ -26,7 +26,14 @@ export type TUpdateSamlCfgDTO = Partial<{
TOrgPermission;
export type TGetSamlCfgDTO =
| { type: "org"; orgId: string; actor: ActorType; actorId: string; actorOrgId?: string }
| {
type: "org";
orgId: string;
actor: ActorType;
actorId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
}
| {
type: "orgSlug";
orgSlug: string;
@ -37,7 +44,8 @@ export type TGetSamlCfgDTO =
};
export type TSamlLoginDTO = {
email: string;
username: string;
email?: string;
firstName: string;
lastName?: string;
authProvider: string;

@ -20,34 +20,38 @@ export const buildScimUserList = ({
export const buildScimUser = ({
userId,
username,
email,
firstName,
lastName,
email,
active
}: {
userId: string;
username: string;
email?: string | null;
firstName: string;
lastName: string;
email: string;
active: boolean;
}): TScimUser => {
return {
const scimUser = {
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
id: userId,
userName: email,
userName: username,
displayName: `${firstName} ${lastName}`,
name: {
givenName: firstName,
middleName: null,
familyName: lastName
},
emails: [
{
primary: true,
value: email,
type: "work"
}
],
emails: email
? [
{
primary: true,
value: email,
type: "work"
}
]
: [],
active,
groups: [],
meta: {
@ -55,4 +59,6 @@ export const buildScimUser = ({
location: null
}
};
return scimUser;
};

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
@ -56,8 +56,16 @@ export const scimServiceFactory = ({
permissionService,
smtpService
}: TScimServiceFactoryDep) => {
const createScimToken = async ({ actor, actorId, actorOrgId, orgId, description, ttlDays }: TCreateScimTokenDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const createScimToken = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
orgId,
description,
ttlDays
}: TCreateScimTokenDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId);
@ -85,8 +93,8 @@ export const scimServiceFactory = ({
return { scimToken };
};
const listScimTokens = async ({ actor, actorId, actorOrgId, orgId }: TOrgPermission) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const listScimTokens = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TOrgPermission) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId);
@ -99,11 +107,17 @@ export const scimServiceFactory = ({
return scimTokens;
};
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorOrgId }: TDeleteScimTokenDTO) => {
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
let scimToken = await scimDAL.findById(scimTokenId);
if (!scimToken) throw new BadRequestError({ message: "Failed to find SCIM token to delete" });
const { permission } = await permissionService.getOrgPermission(actor, actorId, scimToken.orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
scimToken.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(scimToken.orgId);
@ -146,15 +160,16 @@ export const scimServiceFactory = ({
const users = await orgDAL.findMembership(
{
orgId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId,
...parseFilter(filter)
},
findOpts
);
const scimUsers = users.map(({ userId, firstName, lastName, email }) =>
const scimUsers = users.map(({ userId, username, firstName, lastName, email }) =>
buildScimUser({
userId: userId ?? "",
username,
firstName: firstName ?? "",
lastName: lastName ?? "",
email,
@ -173,7 +188,7 @@ export const scimServiceFactory = ({
const [membership] = await orgDAL
.findMembership({
userId,
orgId
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
})
.catch(() => {
throw new ScimRequestError({
@ -196,14 +211,15 @@ export const scimServiceFactory = ({
return buildScimUser({
userId: membership.userId as string,
username: membership.username,
email: membership.email ?? "",
firstName: membership.firstName as string,
lastName: membership.lastName as string,
email: membership.email,
active: true
});
};
const createScimUser = async ({ firstName, lastName, email, orgId }: TCreateScimUserDTO) => {
const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
const org = await orgDAL.findById(orgId);
if (!org)
@ -219,12 +235,18 @@ export const scimServiceFactory = ({
});
let user = await userDAL.findOne({
email
username
});
if (user) {
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership({ userId: user.id, orgId }, { tx });
const [orgMembership] = await orgDAL.findMembership(
{
userId: user.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
},
{ tx }
);
if (orgMembership)
throw new ScimRequestError({
detail: "User already exists in the database",
@ -248,6 +270,7 @@ export const scimServiceFactory = ({
user = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create(
{
username,
email,
firstName,
lastName,
@ -272,21 +295,25 @@ export const scimServiceFactory = ({
}
const appCfg = getConfig();
await smtpService.sendMail({
template: SmtpTemplates.ScimUserProvisioned,
subjectLine: "Infisical organization invitation",
recipients: [email],
substitutions: {
organizationName: org.name,
callback_url: `${appCfg.SITE_URL}/api/v1/sso/redirect/saml2/organizations/${org.slug}`
}
});
if (email) {
await smtpService.sendMail({
template: SmtpTemplates.ScimUserProvisioned,
subjectLine: "Infisical organization invitation",
recipients: [email],
substitutions: {
organizationName: org.name,
callback_url: `${appCfg.SITE_URL}/api/v1/sso/redirect/saml2/organizations/${org.slug}`
}
});
}
return buildScimUser({
userId: user.id,
username: user.username,
firstName: user.firstName as string,
lastName: user.lastName as string,
email: user.email,
email: user.email ?? "",
active: true
});
};
@ -295,7 +322,7 @@ export const scimServiceFactory = ({
const [membership] = await orgDAL
.findMembership({
userId,
orgId
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
})
.catch(() => {
throw new ScimRequestError({
@ -342,9 +369,10 @@ export const scimServiceFactory = ({
return buildScimUser({
userId: membership.userId as string,
username: membership.username,
email: membership.email,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
email: membership.email,
active
});
};
@ -353,7 +381,7 @@ export const scimServiceFactory = ({
const [membership] = await orgDAL
.findMembership({
userId,
orgId
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
})
.catch(() => {
throw new ScimRequestError({
@ -387,9 +415,10 @@ export const scimServiceFactory = ({
return buildScimUser({
userId: membership.userId as string,
username: membership.username,
email: membership.email,
firstName: membership.firstName as string,
lastName: membership.lastName as string,
email: membership.email,
active
});
};

@ -32,7 +32,8 @@ export type TGetScimUserDTO = {
};
export type TCreateScimUserDTO = {
email: string;
username: string;
email?: string;
firstName: string;
lastName: string;
orgId: string;

@ -45,6 +45,7 @@ export const secretApprovalPolicyServiceFactory = ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
approvals,
approvers,
projectId,
@ -54,7 +55,13 @@ export const secretApprovalPolicyServiceFactory = ({
if (approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
@ -98,6 +105,7 @@ export const secretApprovalPolicyServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
approvals,
secretPolicyId
}: TUpdateSapDTO) => {
@ -108,6 +116,7 @@ export const secretApprovalPolicyServiceFactory = ({
actor,
actorId,
secretApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@ -152,7 +161,13 @@ export const secretApprovalPolicyServiceFactory = ({
};
};
const deleteSecretApprovalPolicy = async ({ secretPolicyId, actor, actorId, actorOrgId }: TDeleteSapDTO) => {
const deleteSecretApprovalPolicy = async ({
secretPolicyId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TDeleteSapDTO) => {
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
@ -160,6 +175,7 @@ export const secretApprovalPolicyServiceFactory = ({
actor,
actorId,
sapPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
@ -171,8 +187,20 @@ export const secretApprovalPolicyServiceFactory = ({
return sapPolicy;
};
const getSecretApprovalPolicyByProjectId = async ({ actorId, actor, actorOrgId, projectId }: TListSapDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const getSecretApprovalPolicyByProjectId = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectId
}: TListSapDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId });
@ -201,10 +229,17 @@ export const secretApprovalPolicyServiceFactory = ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
environment,
secretPath
}: TGetBoardSapDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { secretPath, environment })

@ -12,9 +12,11 @@ import { groupBy, pick, unique } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
@ -44,10 +46,12 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretApprovalRequestSecretDAL: TSecretApprovalRequestSecretDALFactory;
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findById" | "findSecretPathByFolderIds">;
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById">;
secretDAL: TSecretDALFactory;
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret">;
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
secretService: Pick<
TSecretServiceFactory,
@ -64,8 +68,10 @@ export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretAppro
export const secretApprovalRequestServiceFactory = ({
secretApprovalRequestDAL,
secretDAL,
folderDAL,
secretTagDAL,
secretVersionTagDAL,
secretApprovalRequestReviewerDAL,
secretApprovalRequestSecretDAL,
secretBlindIndexDAL,
@ -76,13 +82,14 @@ export const secretApprovalRequestServiceFactory = ({
secretVersionDAL,
secretQueueService
}: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId }: TApprovalRequestCountDTO) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { membership } = await permissionService.getProjectPermission(
actor as ActorType.USER,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
@ -94,6 +101,7 @@ export const secretApprovalRequestServiceFactory = ({
projectId,
actorId,
actor,
actorAuthMethod,
actorOrgId,
status,
environment,
@ -103,7 +111,13 @@ export const secretApprovalRequestServiceFactory = ({
}: TListApprovalsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { membership } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
const approvals = await secretApprovalRequestDAL.findByProjectId({
projectId,
committer,
@ -116,21 +130,28 @@ export const secretApprovalRequestServiceFactory = ({
return approvals;
};
const getSecretApprovalDetails = async ({ actor, actorId, actorOrgId, id }: TSecretApprovalDetailsDTO) => {
const getSecretApprovalDetails = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
id
}: TSecretApprovalDetailsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
const { policy } = secretApprovalRequest;
const { membership } = await permissionService.getProjectPermission(
const { membership, hasRole } = await permissionService.getProjectPermission(
actor,
actorId,
secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
if (
membership.role !== ProjectMembershipRole.Admin &&
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
) {
@ -144,20 +165,28 @@ export const secretApprovalRequestServiceFactory = ({
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
};
const reviewApproval = async ({ approvalId, actor, status, actorId, actorOrgId }: TReviewRequestDTO) => {
const reviewApproval = async ({
approvalId,
actor,
status,
actorId,
actorAuthMethod,
actorOrgId
}: TReviewRequestDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy } = secretApprovalRequest;
const { membership } = await permissionService.getProjectPermission(
const { membership, hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
if (
membership.role !== ProjectMembershipRole.Admin &&
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
) {
@ -186,20 +215,28 @@ export const secretApprovalRequestServiceFactory = ({
return reviewStatus;
};
const updateApprovalStatus = async ({ actorId, status, approvalId, actor, actorOrgId }: TStatusChangeDTO) => {
const updateApprovalStatus = async ({
actorId,
status,
approvalId,
actor,
actorOrgId,
actorAuthMethod
}: TStatusChangeDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy } = secretApprovalRequest;
const { membership } = await permissionService.getProjectPermission(
const { membership, hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
if (
membership.role !== ProjectMembershipRole.Admin &&
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
) {
@ -223,16 +260,24 @@ export const secretApprovalRequestServiceFactory = ({
approvalId,
actor,
actorId,
actorOrgId
actorOrgId,
actorAuthMethod
}: TMergeSecretApprovalRequestDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy, folderId, projectId } = secretApprovalRequest;
const { membership } = await permissionService.getProjectPermission(ActorType.USER, actorId, projectId, actorOrgId);
const { membership, hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
if (
membership.role !== ProjectMembershipRole.Admin &&
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
) {
@ -335,7 +380,11 @@ export const secretApprovalRequestServiceFactory = ({
tags: el?.tags.map(({ id }) => id),
version: 1,
type: SecretType.Shared
}))
})),
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL
})
: [];
const updatedSecrets = secretUpdationCommits.length
@ -367,7 +416,11 @@ export const secretApprovalRequestServiceFactory = ({
"secretBlindIndex"
])
}
}))
})),
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL
})
: [];
const deletedSecret = secretDeletionCommits.length
@ -419,6 +472,7 @@ export const secretApprovalRequestServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
policy,
projectId,
secretPath,
@ -430,6 +484,7 @@ export const secretApprovalRequestServiceFactory = ({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
@ -455,7 +510,8 @@ export const secretApprovalRequestServiceFactory = ({
inputSecrets: createdSecrets,
folderId,
isNew: true,
blindIndexCfg
blindIndexCfg,
secretDAL
});
commits.push(
@ -482,7 +538,8 @@ export const secretApprovalRequestServiceFactory = ({
inputSecrets: updatedSecrets,
folderId,
isNew: false,
blindIndexCfg
blindIndexCfg,
secretDAL
});
// now find any secret that needs to update its name
@ -492,7 +549,8 @@ export const secretApprovalRequestServiceFactory = ({
inputSecrets: nameUpdatedSecrets,
folderId,
isNew: true,
blindIndexCfg
blindIndexCfg,
secretDAL
});
const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex as string);
@ -531,7 +589,8 @@ export const secretApprovalRequestServiceFactory = ({
inputSecrets: deletedSecrets,
folderId,
isNew: false,
blindIndexCfg
blindIndexCfg,
secretDAL
});
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
if (!i.secretBlindIndex) throw new BadRequestError({ message: "Missing secret blind index" });

@ -9,6 +9,7 @@ import jmespath from "jmespath";
import knex from "knex";
import { getConfig } from "@app/lib/config/env";
import { getDbConnectionHost } from "@app/lib/knex";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TAssignOp, TDbProviderClients, TDirectAssignOp, THttpProviderFunction } from "../templates/types";
@ -89,7 +90,7 @@ export const secretRotationDbFn = async ({
const appCfg = getConfig();
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
if (host === "localhost" || host === "127.0.0.1" || appCfg.DB_CONNECTION_URI.includes(host))
if (host === "localhost" || host === "127.0.0.1" || getDbConnectionHost(appCfg.DB_CONNECTION_URI) === host)
throw new Error("Invalid db host");
const db = knex({

@ -1,3 +1,10 @@
import {
CreateAccessKeyCommand,
DeleteAccessKeyCommand,
GetAccessKeyLastUsedCommand,
IAMClient
} from "@aws-sdk/client-iam";
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import {
@ -18,7 +25,12 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
import { TSecretRotationDALFactory } from "../secret-rotation-dal";
import { rotationTemplates } from "../templates";
import { TDbProviderClients, TProviderFunctionTypes, TSecretRotationProviderTemplate } from "../templates/types";
import {
TAwsProviderSystems,
TDbProviderClients,
TProviderFunctionTypes,
TSecretRotationProviderTemplate
} from "../templates/types";
import {
getDbSetQuery,
secretRotationDbFn,
@ -127,7 +139,10 @@ export const secretRotationQueueFactory = ({
internal: {}
};
// when its a database we keep cycling the variables accordingly
/* Rotation Function For Database
* A database like sql cannot have multiple password for a user
* thus we ask users to create two users with required permission and then we keep cycling between these two db users
*/
if (provider.template.type === TProviderFunctionTypes.DB) {
const lastCred = variables.creds.at(-1);
if (lastCred && variables.creds.length === 1) {
@ -170,6 +185,65 @@ export const secretRotationQueueFactory = ({
if (variables.creds.length === 2) variables.creds.pop();
}
/*
* Rotation Function For AWS Services
* Due to complexity in AWS Authorization hashing signature process we keep it as seperate entity instead of http template mode
* We first delete old key before creating a new one because aws iam has a quota limit of 2 keys
* */
if (provider.template.type === TProviderFunctionTypes.AWS) {
if (provider.template.client === TAwsProviderSystems.IAM) {
const client = new IAMClient({
region: newCredential.inputs.manager_user_aws_region as string,
credentials: {
accessKeyId: newCredential.inputs.manager_user_access_key as string,
secretAccessKey: newCredential.inputs.manager_user_secret_key as string
}
});
const iamUserName = newCredential.inputs.iam_username as string;
if (variables.creds.length === 2) {
const deleteCycleCredential = variables.creds.pop();
if (deleteCycleCredential) {
const deletedIamAccessKey = await client.send(
new DeleteAccessKeyCommand({
UserName: iamUserName,
AccessKeyId: deleteCycleCredential.outputs.iam_user_access_key as string
})
);
if (
!deletedIamAccessKey?.$metadata?.httpStatusCode ||
deletedIamAccessKey?.$metadata?.httpStatusCode > 300
) {
throw new DisableRotationErrors({
message: "Failed to delete aws iam access key. Check managed iam user policy"
});
}
}
}
const newIamAccessKey = await client.send(new CreateAccessKeyCommand({ UserName: iamUserName }));
if (!newIamAccessKey.AccessKey)
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
// test
const testAccessKey = await client.send(
new GetAccessKeyLastUsedCommand({ AccessKeyId: newIamAccessKey.AccessKey.AccessKeyId })
);
if (testAccessKey?.UserName !== iamUserName)
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
newCredential.outputs.iam_user_access_key = newIamAccessKey.AccessKey.AccessKeyId;
newCredential.outputs.iam_user_secret_key = newIamAccessKey.AccessKey.SecretAccessKey;
}
}
/* Rotation function of HTTP infisical template
* This is a generic http based template system for rotation
* we use this for sendgrid and for custom secret rotation
* This will ensure user provided rotation is easier to make
* */
if (provider.template.type === TProviderFunctionTypes.HTTP) {
if (provider.template.functions.set?.pre) {
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
@ -185,6 +259,9 @@ export const secretRotationQueueFactory = ({
}
}
}
// insert the new variables to start
// encrypt the data - save it
variables.creds.unshift({
outputs: newCredential.outputs,
internal: newCredential.internal
@ -200,6 +277,7 @@ export const secretRotationQueueFactory = ({
key
)
}));
// map the final values to output keys in the board
await secretRotationDAL.transaction(async (tx) => {
await secretRotationDAL.updateById(
rotationId,

@ -39,8 +39,20 @@ export const secretRotationServiceFactory = ({
folderDAL,
secretDAL
}: TSecretRotationServiceFactoryDep) => {
const getProviderTemplates = async ({ actor, actorId, actorOrgId, projectId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const getProviderTemplates = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
return {
@ -54,6 +66,7 @@ export const secretRotationServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
inputs,
outputs,
interval,
@ -61,7 +74,13 @@ export const secretRotationServiceFactory = ({
secretPath,
environment
}: TCreateSecretRotationDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
@ -139,14 +158,20 @@ export const secretRotationServiceFactory = ({
return secretRotation;
};
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId }: TListByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
const doc = await secretRotationDAL.find({ projectId });
return doc;
};
const restartById = async ({ actor, actorId, actorOrgId, rotationId }: TRestartDTO) => {
const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => {
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
@ -157,18 +182,30 @@ export const secretRotationServiceFactory = ({
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
});
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
doc.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
await secretRotationQueue.addToQueue(doc.id, doc.interval);
return doc;
};
const deleteById = async ({ actor, actorId, actorOrgId, rotationId }: TDeleteDTO) => {
const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TDeleteDTO) => {
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new BadRequestError({ message: "Rotation not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, doc.projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
doc.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation

@ -0,0 +1,21 @@
import { TAwsProviderSystems, TProviderFunctionTypes } from "./types";
export const AWS_IAM_TEMPLATE = {
type: TProviderFunctionTypes.AWS as const,
client: TAwsProviderSystems.IAM,
inputs: {
type: "object" as const,
properties: {
manager_user_access_key: { type: "string" as const },
manager_user_secret_key: { type: "string" as const },
manager_user_aws_region: { type: "string" as const },
iam_username: { type: "string" as const }
},
required: ["manager_user_access_key", "manager_user_secret_key", "manager_user_aws_region", "iam_username"],
additionalProperties: false
},
outputs: {
iam_user_access_key: { type: "string" },
iam_user_secret_key: { type: "string" }
}
};

@ -1,3 +1,4 @@
import { AWS_IAM_TEMPLATE } from "./aws-iam";
import { MYSQL_TEMPLATE } from "./mysql";
import { POSTGRES_TEMPLATE } from "./postgres";
import { SENDGRID_TEMPLATE } from "./sendgrid";
@ -24,5 +25,12 @@ export const rotationTemplates: TSecretRotationProviderTemplate[] = [
image: "mysql.png",
description: "Rotate MySQL@7/MariaDB user credentials",
template: MYSQL_TEMPLATE
},
{
name: "aws-iam",
title: "AWS IAM",
image: "aws-iam.svg",
description: "Rotate AWS IAM User credentials",
template: AWS_IAM_TEMPLATE
}
];

@ -1,6 +1,7 @@
export enum TProviderFunctionTypes {
HTTP = "http",
DB = "database"
DB = "database",
AWS = "aws"
}
export enum TDbProviderClients {
@ -10,6 +11,10 @@ export enum TDbProviderClients {
MySql = "mysql"
}
export enum TAwsProviderSystems {
IAM = "iam"
}
export enum TAssignOp {
Direct = "direct",
JmesPath = "jmesopath"
@ -42,7 +47,7 @@ export type TSecretRotationProviderTemplate = {
title: string;
image?: string;
description?: string;
template: THttpProviderTemplate | TDbProviderTemplate;
template: THttpProviderTemplate | TDbProviderTemplate | TAwsProviderTemplate;
};
export type THttpProviderTemplate = {
@ -70,3 +75,14 @@ export type TDbProviderTemplate = {
};
outputs: Record<string, unknown>;
};
export type TAwsProviderTemplate = {
type: TProviderFunctionTypes.AWS;
client: TAwsProviderSystems;
inputs: {
type: "object";
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
required?: string[];
};
outputs: Record<string, unknown>;
};

@ -64,7 +64,7 @@ export const secretScanningQueueFactory = ({
orgId: organizationId,
role: OrgMembershipRole.Admin
});
return adminsOfWork.map((userObject) => userObject.email);
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);
};
queueService.start(QueueName.SecretPushEventScan, async (job) => {
@ -149,7 +149,7 @@ export const secretScanningQueueFactory = ({
await smtpService.sendMail({
template: SmtpTemplates.SecretLeakIncident,
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
recipients: adminEmails,
recipients: adminEmails.filter((email) => email).map((email) => email),
substitutions: {
numberOfSecrets: Object.keys(allFindingsByFingerprint).length,
pusher_email: pusher.email,
@ -221,7 +221,7 @@ export const secretScanningQueueFactory = ({
await smtpService.sendMail({
template: SmtpTemplates.SecretLeakIncident,
subjectLine: `Incident alert: leaked secrets found in Github repository ${repository.fullName}`,
recipients: adminEmails,
recipients: adminEmails.filter((email) => email).map((email) => email),
substitutions: {
numberOfSecrets: findings.length
}

@ -39,8 +39,14 @@ export const secretScanningServiceFactory = ({
permissionService,
secretScanningQueue
}: TSecretScanningServiceFactoryDep) => {
const createInstallationSession = async ({ actor, orgId, actorId, actorOrgId }: TInstallAppSessionDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const createInstallationSession = async ({
actor,
orgId,
actorId,
actorAuthMethod,
actorOrgId
}: TInstallAppSessionDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const sessionId = crypto.randomBytes(16).toString("hex");
@ -53,12 +59,19 @@ export const secretScanningServiceFactory = ({
actorId,
installationId,
actor,
actorAuthMethod,
actorOrgId
}: TLinkInstallSessionDTO) => {
const session = await gitAppInstallSessionDAL.findOne({ sessionId });
if (!session) throw new UnauthorizedError({ message: "Session not found" });
const { permission } = await permissionService.getOrgPermission(actor, actorId, session.orgId, actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
session.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const installatedApp = await gitAppOrgDAL.transaction(async (tx) => {
await gitAppInstallSessionDAL.deleteById(session.id, tx);
@ -89,23 +102,37 @@ export const secretScanningServiceFactory = ({
return { installatedApp };
};
const getOrgInstallationStatus = async ({ actorId, orgId, actor, actorOrgId }: TGetOrgInstallStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getOrgInstallationStatus = async ({
actorId,
orgId,
actor,
actorAuthMethod,
actorOrgId
}: TGetOrgInstallStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const appInstallation = await gitAppOrgDAL.findOne({ orgId });
return Boolean(appInstallation);
};
const getRisksByOrg = async ({ actor, orgId, actorId, actorOrgId }: TGetOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
return { risks };
};
const updateRiskStatus = async ({ actorId, orgId, actor, actorOrgId, riskId, status }: TUpdateRiskStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorOrgId);
const updateRiskStatus = async ({
actorId,
orgId,
actor,
actorOrgId,
actorAuthMethod,
riskId,
status
}: TUpdateRiskStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
const isRiskResolved = Boolean(

@ -59,9 +59,16 @@ export const secretSnapshotServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
path
}: TProjectSnapshotCountDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
@ -77,11 +84,18 @@ export const secretSnapshotServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
path,
limit = 20,
offset = 0
}: TProjectSnapshotListDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
@ -91,10 +105,16 @@ export const secretSnapshotServiceFactory = ({
return snapshots;
};
const getSnapshotData = async ({ actorId, actor, actorOrgId, id }: TGetSnapshotDataDTO) => {
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
const snapshot = await snapshotDAL.findSecretSnapshotDataById(id);
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
snapshot.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
return snapshot;
};
@ -145,11 +165,23 @@ export const secretSnapshotServiceFactory = ({
}
};
const rollbackSnapshot = async ({ id: snapshotId, actor, actorId, actorOrgId }: TRollbackSnapshotDTO) => {
const rollbackSnapshot = async ({
id: snapshotId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TRollbackSnapshotDTO) => {
const snapshot = await snapshotDAL.findById(snapshotId);
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
const { permission } = await permissionService.getProjectPermission(actor, actorId, snapshot.projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
snapshot.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback

@ -26,8 +26,14 @@ export const trustedIpServiceFactory = ({
licenseService,
projectDAL
}: TTrustedIpServiceFactoryDep) => {
const listIpsByProjectId = async ({ projectId, actor, actorId, actorOrgId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
const trustedIps = await trustedIpDAL.find({
projectId
@ -38,13 +44,20 @@ export const trustedIpServiceFactory = ({
const addProjectIp = async ({
projectId,
actorId,
actorAuthMethod,
actor,
actorOrgId,
ipAddress: ip,
comment,
isActive
}: TCreateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);
@ -78,11 +91,18 @@ export const trustedIpServiceFactory = ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
ipAddress: ip,
comment,
trustedIpId
}: TUpdateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);
@ -113,8 +133,21 @@ export const trustedIpServiceFactory = ({
return { trustedIp, project }; // for audit log
};
const deleteProjectIp = async ({ projectId, actorId, actor, actorOrgId, trustedIpId }: TDeleteIpDTO) => {
const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId, actorOrgId);
const deleteProjectIp = async ({
projectId,
actorId,
actor,
actorOrgId,
actorAuthMethod,
trustedIpId
}: TDeleteIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);

@ -0,0 +1,287 @@
export const IDENTITIES = {
CREATE: {
name: "The name of the identity to create.",
organizationId: "The organization ID to which the identity belongs.",
role: "The role of the identity. Possible values are 'no-access', 'member', and 'admin'."
},
UPDATE: {
identityId: "The ID of the identity to update.",
name: "The new name of the identity.",
role: "The new role of the identity."
},
DELETE: {
identityId: "The ID of the identity to delete."
}
} as const;
export const UNIVERSAL_AUTH = {
LOGIN: {
clientId: "Your Machine Identity Client ID.",
clientSecret: "Your Machine Identity Client Secret."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
clientSecretTrustedIps:
"A list of IPs or CIDR ranges that the Client Secret can be used from together with the Client ID to get back an access token. You can use 0.0.0.0/0, to allow usage from any network address.",
accessTokenTrustedIps:
"A list of IPs or CIDR ranges that access tokens can be used from. You can use 0.0.0.0/0, to allow usage from any network address.",
accessTokenTTL: "The lifetime for an access token in seconds. This value will be referenced at renewal time.",
accessTokenMaxTTL:
"The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.",
accessTokenNumUsesLimit:
"The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve."
},
UPDATE: {
identityId: "The ID of the identity to update.",
clientSecretTrustedIps: "The new list of IPs or CIDR ranges that the Client Secret can be used from.",
accessTokenTrustedIps: "The new list of IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
},
CREATE_CLIENT_SECRET: {
identityId: "The ID of the identity to create a client secret for.",
description: "The description of the client secret.",
numUsesLimit:
"The maximum number of times that the client secret can be used; a value of 0 implies infinite number of uses.",
ttl: "The lifetime for the client secret in seconds."
},
LIST_CLIENT_SECRETS: {
identityId: "The ID of the identity to list client secrets for."
},
REVOKE_CLIENT_SECRET: {
identityId: "The ID of the identity to revoke the client secret from.",
clientSecretId: "The ID of the client secret to revoke."
},
RENEW_ACCESS_TOKEN: {
accessToken: "The access token to renew."
}
} as const;
export const ORGANIZATIONS = {
LIST_USER_MEMBERSHIPS: {
organizationId: "The ID of the organization to get memberships from."
},
UPDATE_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to update the membership for.",
membershipId: "The ID of the membership to update.",
role: "The new role of the membership."
},
DELETE_USER_MEMBERSHIP: {
organizationId: "The ID of the organization to delete the membership from.",
membershipId: "The ID of the membership to delete."
},
LIST_IDENTITY_MEMBERSHIPS: {
orgId: "The ID of the organization to get identity memberships from."
},
GET_PROJECTS: {
organizationId: "The ID of the organization to get projects from."
}
} as const;
export const PROJECTS = {
CREATE: {
organizationSlug: "The slug of the organization to create the project in.",
projectName: "The name of the project to create.",
slug: "An optional slug for the project."
},
DELETE: {
workspaceId: "The ID of the project to delete."
},
GET: {
workspaceId: "The ID of the project."
},
UPDATE: {
workspaceId: "The ID of the project to update.",
name: "The new name of the project.",
autoCapitalization: "Disable or enable auto-capitalization for the project."
},
INVITE_MEMBER: {
projectId: "The ID of the project to invite the member to.",
emails: "A list of organization member emails to invite to the project.",
usernames: "A list of usernames to invite to the project."
},
REMOVE_MEMBER: {
projectId: "The ID of the project to remove the member from.",
emails: "A list of organization member emails to remove from the project.",
usernames: "A list of usernames to remove from the project."
},
GET_USER_MEMBERSHIPS: {
workspaceId: "The ID of the project to get memberships from."
},
UPDATE_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to update the membership for.",
membershipId: "The ID of the membership to update.",
roles: "A list of roles to update the membership to."
},
LIST_IDENTITY_MEMBERSHIPS: {
projectId: "The ID of the project to get identity memberships from."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
roles: "A list of roles to update the membership to."
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
},
GET_KEY: {
workspaceId: "The ID of the project to get the key from."
},
GET_SNAPSHOTS: {
workspaceId: "The ID of the project to get snapshots from.",
environment: "The environment to get snapshots from.",
path: "The secret path to get snapshots from.",
offset: "The offset to start from. If you enter 10, it will start from the 10th snapshot.",
limit: "The number of snapshots to return."
},
ROLLBACK_TO_SNAPSHOT: {
secretSnapshotId: "The ID of the snapshot to rollback to."
}
} as const;
export const ENVIRONMENTS = {
CREATE: {
workspaceId: "The ID of the project to create the environment in.",
name: "The name of the environment to create.",
slug: "The slug of the environment to create."
},
UPDATE: {
workspaceId: "The ID of the project to update the environment in.",
id: "The ID of the environment to update.",
name: "The new name of the environment.",
slug: "The new slug of the environment.",
position: "The new position of the environment. The lowest number will be displayed as the first environment."
},
DELETE: {
workspaceId: "The ID of the project to delete the environment from.",
id: "The ID of the environment to delete."
}
} as const;
export const FOLDERS = {
LIST: {
workspaceId: "The ID of the project to list folders from.",
environment: "The slug of the environment to list folders from.",
path: "The path to list folders from.",
directory: "The directory to list folders from. (Deprecated in favor of path)"
},
CREATE: {
workspaceId: "The ID of the project to create the folder in.",
environment: "The slug of the environment to create the folder in.",
name: "The name of the folder to create.",
path: "The path of the folder to create.",
directory: "The directory of the folder to create. (Deprecated in favor of path)"
},
UPDATE: {
folderId: "The ID of the folder to update.",
environment: "The slug of the environment where the folder is located.",
name: "The new name of the folder.",
path: "The path of the folder to update.",
directory: "The new directory of the folder to update. (Deprecated in favor of path)",
workspaceId: "The ID of the project where the folder is located."
},
DELETE: {
folderIdOrName: "The ID or name of the folder to delete.",
workspaceId: "The ID of the project to delete the folder from.",
environment: "The slug of the environment where the folder is located.",
directory: "The directory of the folder to delete. (Deprecated in favor of path)",
path: "The path of the folder to delete."
}
} as const;
export const RAW_SECRETS = {
LIST: {
workspaceId: "The ID of the project to list secrets from.",
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.",
environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.",
includeImports: "Weather to include imported secrets or not."
},
CREATE: {
secretName: "The name of the secret to create.",
environment: "The slug of the environment to create the secret in.",
secretComment: "Attach a comment to the secret.",
secretPath: "The path to create the secret in.",
secretValue: "The value of the secret to create.",
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
type: "The type of the secret to create.",
workspaceId: "The ID of the project to create the secret in."
},
GET: {
secretName: "The name of the secret to get.",
workspaceId: "The ID of the project to get the secret from.",
environment: "The slug of the environment to get the secret from.",
secretPath: "The path of the secret to get.",
version: "The version of the secret to get.",
type: "The type of the secret to get.",
includeImports: "Weather to include imported secrets or not."
},
UPDATE: {
secretName: "The name of the secret to update.",
environment: "The slug of the environment where the secret is located.",
secretPath: "The path of the secret to update",
secretValue: "The new value of the secret.",
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
type: "The type of the secret to update.",
workspaceId: "The ID of the project to update the secret in."
},
DELETE: {
secretName: "The name of the secret to delete.",
environment: "The slug of the environment where the secret is located.",
secretPath: "The path of the secret.",
type: "The type of the secret to delete.",
workspaceId: "The ID of the project where the secret is located."
}
} as const;
export const SECRET_IMPORTS = {
LIST: {
workspaceId: "The ID of the project to list secret imports from.",
environment: "The slug of the environment to list secret imports from.",
path: "The path to list secret imports from."
},
CREATE: {
environment: "The slug of the environment to import into.",
path: "The path to import into.",
workspaceId: "The ID of the project you are working in.",
import: {
environment: "The slug of the environment to import from.",
path: "The path to import from."
}
},
UPDATE: {
secretImportId: "The ID of the secret import to update.",
environment: "The slug of the environment where the secret import is located.",
import: {
environment: "The new environment slug to import from.",
path: "The new path to import from.",
position: "The new position of the secret import. The lowest number will be displayed as the first import."
},
path: "The path of the secret import to update.",
workspaceId: "The ID of the project where the secret import is located."
},
DELETE: {
workspaceId: "The ID of the project to delete the secret import from.",
secretImportId: "The ID of the secret import to delete.",
environment: "The slug of the environment where the secret import is located.",
path: "The path of the secret import to delete."
}
} as const;
export const AUDIT_LOGS = {
EXPORT: {
workspaceId: "The ID of the project to export audit logs from.",
eventType: "The type of the event to export.",
userAgentType: "Choose which consuming application to export audit logs for.",
startDate: "The date to start the export from.",
endDate: "The date to end the export at.",
offset: "The offset to start from. If you enter 10, it will start from the 10th audit log.",
limit: "The number of audit logs to return.",
actor: "The actor to filter the audit logs by."
}
} as const;

@ -0,0 +1 @@
export * from "./constants";

@ -15,8 +15,16 @@ const envSchema = z
PORT: z.coerce.number().default(4000),
REDIS_URL: zpStr(z.string()),
HOST: zpStr(z.string().default("localhost")),
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")),
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
),
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
DB_PORT: zpStr(z.string().describe("Postgres database port").optional()).default("5432"),
DB_USER: zpStr(z.string().describe("Postgres database username").optional()),
DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()),
DB_NAME: zpStr(z.string().describe("Postgres database name").optional()),
NODE_ENV: z.enum(["development", "test", "production"]).default("production"),
SALT_ROUNDS: z.coerce.number().default(10),
INITIAL_ORGANIZATION_NAME: zpStr(z.string().optional()),
@ -98,6 +106,7 @@ const envSchema = z
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
LICENSE_KEY: zpStr(z.string().optional()),
LICENSE_KEY_OFFLINE: zpStr(z.string().optional()),
// GENERIC
STANDALONE_MODE: z

@ -17,4 +17,5 @@ export {
decryptSecrets,
decryptSecretVersions
} from "./secret-encryption";
export { verifyOfflineLicense } from "./signing";
export { generateSrpServerKey, srpCheckClientProof } from "./srp";

@ -0,0 +1,8 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEApchBY3BXTu4zWGBguB7nM/pjpVLY3V7VGZOAxmR5ueQTJOwiGM13
5HN3EM9fDlQnZu9VSc0OFqRM/bUeUaI1oLPE6WzTHjdHyKjDI/S+TLx3VGEsvhM1
uukZpYX+3KX2w4wzRHBaBWyglFy0CVNth9UJhhpD+KKfv7dzcRmsbyoUWi9wGfJu
wLYCwaCwZRXIt1sLGmMncPz14vfwdnm2a5Tj1Jbt0GTyBl+1/ZqLbO6SsslLg2G+
o7FfGS9z8OUTkvDdu16qxL+p2wCEFZMnOz5BB4oakuT2gS9iOO2l5AOPcT4WzPzy
PYbX3d7cN9BkOY9I5z0cX4wzqHjQTvGNLQIDAQAB
-----END RSA PUBLIC KEY-----

@ -0,0 +1,22 @@
import crypto, { KeyObject } from "crypto";
import fs from "fs/promises";
import path from "path";
export const verifySignature = (data: string, signature: Buffer, publicKey: KeyObject) => {
const verify = crypto.createVerify("SHA256");
verify.update(data);
verify.end();
return verify.verify(publicKey, signature);
};
export const verifyOfflineLicense = async (licenseContents: string, signature: string) => {
const publicKeyPem = await fs.readFile(path.join(__dirname, "license_public_key.pem"), "utf8");
const publicKey = crypto.createPublicKey({
key: publicKeyPem,
format: "pem",
type: "pkcs1"
});
return verifySignature(licenseContents, Buffer.from(signature, "base64"), publicKey);
};

@ -0,0 +1,11 @@
import { URL } from "url"; // Import the URL class
export const getDbConnectionHost = (urlString: string) => {
try {
const url = new URL(urlString);
// Split hostname and port (if provided)
return url.hostname.split(":")[0];
} catch (error) {
return null;
}
};

@ -4,6 +4,7 @@ import { Tables } from "knex/types/tables";
import { DatabaseError } from "../errors";
export * from "./connection";
export * from "./join";
export * from "./select";

@ -1,17 +1,19 @@
import { ActorType } from "@app/services/auth/auth-type";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
export type TOrgPermission = {
actor: ActorType;
actorId: string;
orgId: string;
actorOrgId?: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
};
export type TProjectPermission = {
actor: ActorType;
actorId: string;
projectId: string;
actorOrgId?: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined;
};
export type RequiredKeys<T> = {

@ -5,7 +5,7 @@ import { ActorType } from "@app/services/auth/auth-type";
// this is a unique id for sending posthog event
export const getTelemetryDistinctId = (req: FastifyRequest) => {
if (req.auth.actor === ActorType.USER) {
return req.auth.user.email;
return req.auth.user.username;
}
if (req.auth.actor === ActorType.IDENTITY) {
return `identity-${req.auth.identityId}`;

@ -44,6 +44,7 @@ export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
type: ActorType.USER,
metadata: {
email: req.auth.user.email,
username: req.auth.user.username,
userId: req.permission.id
}
};

@ -6,42 +6,49 @@ import { TServiceTokens, TUsers } from "@app/db/schemas";
import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { ActorType, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
export type TAuthMode =
| {
orgId?: string;
authMode: AuthMode.JWT;
actor: ActorType.USER;
userId: string;
tokenVersionId: string; // the session id of token used
user: TUsers;
orgId?: string;
authMethod: AuthMethod;
}
| {
authMode: AuthMode.API_KEY;
authMethod: null;
actor: ActorType.USER;
userId: string;
user: TUsers;
orgId?: string;
orgId: string;
}
| {
authMode: AuthMode.SERVICE_TOKEN;
serviceToken: TServiceTokens & { createdByEmail: string };
actor: ActorType.SERVICE;
serviceTokenId: string;
orgId: string;
authMethod: null;
}
| {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN;
actor: ActorType.IDENTITY;
identityId: string;
identityName: string;
orgId: string;
authMethod: null;
}
| {
authMode: AuthMode.SCIM_TOKEN;
actor: ActorType.SCIM_CLIENT;
scimTokenId: string;
orgId: string;
authMethod: null;
};
const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
@ -50,6 +57,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
return { authMode: AuthMode.API_KEY, token: apiKey, actor: ActorType.USER } as const;
}
const authHeader = req.headers?.authorization;
if (!authHeader) return { authMode: null, token: null };
const authTokenValue = authHeader.slice(7); // slice of after Bearer
@ -71,6 +79,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
actor: ActorType.USER
} as const;
case AuthTokenType.API_KEY:
// throw new Error("API Key auth is no longer supported.");
return { authMode: AuthMode.API_KEY, token: decodedToken, actor: ActorType.USER } as const;
case AuthTokenType.IDENTITY_ACCESS_TOKEN:
return {
@ -89,17 +98,30 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
}
};
// ! Important: You can only 100% count on the `req.permission.orgId` field being present when the auth method is Identity Access Token (Machine Identity).
export const injectIdentity = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auth", null);
server.addHook("onRequest", async (req) => {
const appCfg = getConfig();
const { authMode, token, actor } = await extractAuth(req, appCfg.AUTH_SECRET);
if (req.url.includes("/api/v3/auth/")) {
return;
}
if (!authMode) return;
switch (authMode) {
case AuthMode.JWT: {
const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token);
req.auth = { authMode: AuthMode.JWT, user, userId: user.id, tokenVersionId, actor, orgId };
req.auth = {
authMode: AuthMode.JWT,
user,
userId: user.id,
tokenVersionId,
actor,
orgId,
authMethod: token.authMethod
};
break;
}
case AuthMode.IDENTITY_ACCESS_TOKEN: {
@ -107,29 +129,40 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor,
orgId: identity.orgId,
identityId: identity.identityId,
identityName: identity.name
identityName: identity.name,
authMethod: null
};
break;
}
case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
req.auth = {
orgId: serviceToken.orgId,
authMode: AuthMode.SERVICE_TOKEN as const,
serviceToken,
serviceTokenId: serviceToken.id,
actor
actor,
authMethod: null
};
break;
}
case AuthMode.API_KEY: {
const user = await server.services.apiKey.fnValidateApiKey(token as string);
req.auth = { authMode: AuthMode.API_KEY as const, userId: user.id, actor, user };
req.auth = {
authMode: AuthMode.API_KEY as const,
userId: user.id,
actor,
user,
orgId: "API_KEY", // We set the orgId to an arbitrary value, since we can't link an API key to a specific org. We have to deprecate API keys soon!
authMethod: null
};
break;
}
case AuthMode.SCIM_TOKEN: {
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId };
req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null };
break;
}
default:

@ -9,13 +9,33 @@ export const injectPermission = fp(async (server) => {
if (!req.auth) return;
if (req.auth.actor === ActorType.USER) {
req.permission = { type: ActorType.USER, id: req.auth.userId, orgId: req.auth?.orgId };
req.permission = {
type: ActorType.USER,
id: req.auth.userId,
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
};
} else if (req.auth.actor === ActorType.IDENTITY) {
req.permission = { type: ActorType.IDENTITY, id: req.auth.identityId };
req.permission = {
type: ActorType.IDENTITY,
id: req.auth.identityId,
orgId: req.auth.orgId,
authMethod: null
};
} else if (req.auth.actor === ActorType.SERVICE) {
req.permission = { type: ActorType.SERVICE, id: req.auth.serviceTokenId };
req.permission = {
type: ActorType.SERVICE,
id: req.auth.serviceTokenId,
orgId: req.auth.orgId,
authMethod: null
};
} else if (req.auth.actor === ActorType.SCIM_CLIENT) {
req.permission = { type: ActorType.SCIM_CLIENT, id: req.auth.scimTokenId, orgId: req.auth.orgId };
req.permission = {
type: ActorType.SCIM_CLIENT,
id: req.auth.scimTokenId,
orgId: req.auth.orgId,
authMethod: null
};
}
});
});

@ -3,15 +3,26 @@ import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
import { UnauthorizedError } from "@app/lib/errors";
import { AuthMode } from "@app/services/auth/auth-type";
interface TAuthOptions {
requireOrg: boolean;
}
export const verifyAuth =
<T extends FastifyRequest>(authStrats: AuthMode[]) =>
<T extends FastifyRequest>(authStrategies: AuthMode[], options: TAuthOptions = { requireOrg: true }) =>
(req: T, _res: FastifyReply, done: HookHandlerDoneFunction) => {
if (!Array.isArray(authStrats)) throw new Error("Auth strategy must be array");
if (!Array.isArray(authStrategies)) throw new Error("Auth strategy must be array");
if (!req.auth) throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" });
const isAccessAllowed = authStrats.some((strat) => strat === req.auth.authMode);
const isAccessAllowed = authStrategies.some((strategy) => strategy === req.auth.authMode);
if (!isAccessAllowed) {
throw new UnauthorizedError({ name: `${req.url} Unauthorized Access` });
}
// New optional option. There are some routes which do not require an organization ID to be present on the request.
// An example of this is the /v1 auth routes.
if (req.auth.authMode === AuthMode.JWT && options.requireOrg === true && !req.permission.orgId) {
throw new UnauthorizedError({ name: `${req.url} Unauthorized Access, no organization found in request` });
}
done();
};

@ -14,13 +14,13 @@ export const fastifySwagger = fp(async (fastify) => {
version: "0.0.1"
},
servers: [
{
url: "http://localhost:8080",
description: "Local server"
},
{
url: "https://app.infisical.com",
description: "Production server"
},
{
url: "http://localhost:8080",
description: "Local server"
}
],
components: {

@ -5,6 +5,8 @@ import { registerV1EERoutes } from "@app/ee/routes/v1";
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
@ -51,6 +53,7 @@ import { identityServiceFactory } from "@app/services/identity/identity-service"
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { identityUaClientSecretDALFactory } from "@app/services/identity-ua/identity-ua-client-secret-dal";
import { identityUaDALFactory } from "@app/services/identity-ua/identity-ua-dal";
@ -76,6 +79,7 @@ import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal"
import { projectKeyServiceFactory } from "@app/services/project-key/project-key-service";
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { secretDALFactory } from "@app/services/secret/secret-dal";
@ -102,6 +106,7 @@ import { telemetryQueueServiceFactory } from "@app/services/telemetry/telemetry-
import { telemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
import { userDALFactory } from "@app/services/user/user-dal";
import { userServiceFactory } from "@app/services/user/user-service";
import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
@ -126,6 +131,7 @@ export const registerRoutes = async (
// db layers
const userDAL = userDALFactory(db);
const userAliasDAL = userAliasDALFactory(db);
const authDAL = authDALFactory(db);
const authTokenDAL = tokenDALFactory(db);
const orgDAL = orgDALFactory(db);
@ -137,6 +143,7 @@ export const registerRoutes = async (
const projectDAL = projectDALFactory(db);
const projectMembershipDAL = projectMembershipDALFactory(db);
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(db);
const projectRoleDAL = projectRoleDALFactory(db);
const projectEnvDAL = projectEnvDALFactory(db);
const projectKeyDAL = projectKeyDALFactory(db);
@ -160,18 +167,20 @@ export const registerRoutes = async (
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
const identityOrgMembershipDAL = identityOrgDALFactory(db);
const identityProjectDAL = identityProjectDALFactory(db);
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
const identityUaDAL = identityUaDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const auditLogDAL = auditLogDALFactory(db);
const trustedIpDAL = trustedIpDALFactory(db);
const scimDAL = scimDALFactory(db);
const telemetryDAL = telemetryDALFactory(db);
// ee db layer ops
const permissionDAL = permissionDALFactory(db);
const samlConfigDAL = samlConfigDALFactory(db);
const scimDAL = scimDALFactory(db);
const ldapConfigDAL = ldapConfigDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
@ -192,7 +201,8 @@ export const registerRoutes = async (
permissionDAL,
orgRoleDAL,
projectRoleDAL,
serviceTokenDAL
serviceTokenDAL,
projectDAL
});
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
const trustedIpService = trustedIpServiceFactory({
@ -235,6 +245,16 @@ export const registerRoutes = async (
smtpService
});
const ldapService = ldapConfigServiceFactory({
ldapConfigDAL,
orgDAL,
orgBotDAL,
userDAL,
userAliasDAL,
permissionService,
licenseService
});
const telemetryService = telemetryServiceFactory({
keyStore,
licenseService
@ -247,7 +267,7 @@ export const registerRoutes = async (
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const userService = userServiceFactory({ userDAL });
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService });
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
const passwordService = authPaswordServiceFactory({
tokenService,
smtpService,
@ -263,6 +283,8 @@ export const registerRoutes = async (
incidentContactDAL,
tokenService,
projectDAL,
projectMembershipDAL,
projectKeyDAL,
smtpService,
userDAL,
orgBotDAL
@ -304,6 +326,7 @@ export const registerRoutes = async (
const projectMembershipService = projectMembershipServiceFactory({
projectMembershipDAL,
projectUserMembershipRoleDAL,
projectDAL,
permissionService,
projectBotDAL,
@ -335,7 +358,8 @@ export const registerRoutes = async (
projectBotDAL,
projectMembershipDAL,
secretApprovalRequestDAL,
secretApprovalSecretDAL: sarSecretDAL
secretApprovalSecretDAL: sarSecretDAL,
projectUserMembershipRoleDAL
});
const projectService = projectServiceFactory({
@ -349,11 +373,15 @@ export const registerRoutes = async (
projectKeyDAL,
userDAL,
projectEnvDAL,
orgDAL,
orgService,
projectMembershipDAL,
folderDAL,
licenseService
licenseService,
projectUserMembershipRoleDAL,
identityProjectMembershipRoleDAL
});
const projectEnvService = projectEnvServiceFactory({
permissionService,
projectEnvDAL,
@ -419,7 +447,12 @@ export const registerRoutes = async (
orgDAL,
projectMembershipDAL,
smtpService,
projectDAL
projectDAL,
projectBotDAL,
secretVersionDAL,
secretBlindIndexDAL,
secretTagDAL,
secretVersionTagDAL
});
const secretBlindIndexService = secretBlindIndexServiceFactory({
permissionService,
@ -443,6 +476,7 @@ export const registerRoutes = async (
const sarService = secretApprovalRequestServiceFactory({
permissionService,
folderDAL,
secretDAL,
secretTagDAL,
secretApprovalRequestSecretDAL: sarSecretDAL,
secretApprovalRequestReviewerDAL: sarReviewerDAL,
@ -452,6 +486,7 @@ export const registerRoutes = async (
secretApprovalRequestDAL,
secretService,
snapshotService,
secretVersionTagDAL,
secretQueueService
});
const secretRotationQueue = secretRotationQueueFactory({
@ -484,7 +519,8 @@ export const registerRoutes = async (
projectEnvDAL,
serviceTokenDAL,
userDAL,
permissionService
permissionService,
projectDAL
});
const identityService = identityServiceFactory({
@ -492,12 +528,17 @@ export const registerRoutes = async (
identityDAL,
identityOrgMembershipDAL
});
const identityAccessTokenService = identityAccessTokenServiceFactory({ identityAccessTokenDAL });
const identityAccessTokenService = identityAccessTokenServiceFactory({
identityAccessTokenDAL,
identityOrgMembershipDAL
});
const identityProjectService = identityProjectServiceFactory({
permissionService,
projectDAL,
identityProjectDAL,
identityOrgMembershipDAL
identityOrgMembershipDAL,
identityProjectMembershipRoleDAL,
projectRoleDAL
});
const identityUaService = identityUaServiceFactory({
identityOrgMembershipDAL,
@ -552,6 +593,7 @@ export const registerRoutes = async (
secretRotation: secretRotationService,
snapshot: snapshotService,
saml: samlService,
ldap: ldapService,
auditLog: auditLogService,
secretScanning: secretScanningService,
license: licenseService,

@ -92,9 +92,10 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.AdminInit,
distinctId: user.user.email,
distinctId: user.user.username ?? "",
properties: {
email: user.user.email,
username: user.user.username,
email: user.user.email ?? "",
lastName: user.user.lastName || "",
firstName: user.user.firstName || ""
}

@ -21,7 +21,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
handler: async (req, res) => {
const appCfg = getConfig();
if (req.auth.authMode === AuthMode.JWT) {
@ -85,6 +85,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
const token = jwt.sign(
{
authMethod: decodedToken.authMethod,
authTokenType: AuthTokenType.ACCESS_TOKEN,
userId: decodedToken.userId,
tokenVersionId: tokenVersion.id,

@ -30,6 +30,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
projectId: req.params.projectId
});
return { bot };
@ -70,6 +71,7 @@ export const registerProjectBotRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
botId: req.params.botId,
botKey: req.body.botKey,
isActive: req.body.isActive

@ -1,5 +1,7 @@
import { z } from "zod";
import { UNIVERSAL_AUTH } from "@app/lib/api-docs";
export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/token/renew",
@ -7,7 +9,7 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
schema: {
description: "Renew access token",
body: z.object({
accessToken: z.string().trim()
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.RENEW_ACCESS_TOKEN.accessToken)
}),
response: {
200: z.object({

@ -2,6 +2,7 @@ import { z } from "zod";
import { IdentitiesSchema, OrgMembershipRole } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { IDENTITIES } from "@app/lib/api-docs";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
@ -20,9 +21,9 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
}
],
body: z.object({
name: z.string().trim(),
organizationId: z.string().trim(),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess)
name: z.string().trim().describe(IDENTITIES.CREATE.name),
organizationId: z.string().trim().describe(IDENTITIES.CREATE.organizationId),
role: z.string().trim().min(1).default(OrgMembershipRole.NoAccess).describe(IDENTITIES.CREATE.role)
}),
response: {
200: z.object({
@ -34,6 +35,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
const identity = await server.services.identity.createIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
orgId: req.body.organizationId
@ -78,11 +80,11 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(IDENTITIES.UPDATE.identityId)
}),
body: z.object({
name: z.string().trim().optional(),
role: z.string().trim().min(1).optional()
name: z.string().trim().optional().describe(IDENTITIES.UPDATE.name),
role: z.string().trim().min(1).optional().describe(IDENTITIES.UPDATE.role)
}),
response: {
200: z.object({
@ -94,6 +96,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
const identity = await server.services.identity.updateIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.identityId,
...req.body
@ -127,7 +130,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
identityId: z.string()
identityId: z.string().describe(IDENTITIES.DELETE.identityId)
}),
response: {
200: z.object({
@ -139,6 +142,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
const identity = await server.services.identity.deleteIdentity({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.identityId
});

Some files were not shown because too many files have changed in this diff Show More