Compare commits

...

29 Commits

Author SHA1 Message Date
x032205
5a3aa3d608 log github error 2025-08-04 13:42:00 -04:00
Daniel Hougaard
95b327de50 Merge pull request #4299 from Infisical/daniel/injector-ldap-auth-docs
docs(agent-injector): ldap auth method
2025-08-04 21:26:27 +04:00
Scott Wilson
a3c36f82f3 Merge pull request #4305 from Infisical/add-react-import-to-email-components
fix: add react import to email button component
2025-08-04 10:22:10 -07:00
Scott Wilson
42612da57d Merge pull request #4293 from Infisical/minor-ui-feedback
improvements: adjust secret search padding when no clear icon and fix access approval reviewer tooltips display
2025-08-04 10:20:32 -07:00
Scott Wilson
f63c07d538 fix: add react import to email button component 2025-08-04 10:12:50 -07:00
x032205
98a08d136e Merge pull request #4302 from Infisical/fix-timeout-for-audit-prune
Add timeout to audit log
2025-08-04 12:28:48 -04:00
x032205
6c74b875f3 up to 10 mins 2025-08-04 10:46:10 -04:00
x032205
793cd4c144 Add timeout to audit log 2025-08-04 10:43:25 -04:00
Sid
ec0be1166f feat: Secret reminder from date filter (#4289)
* feat: add fromDate in reminders

* feat: update reminder form

* fix: lint

* chore: generate schema

* fix: reminder logic

* fix: update ui

* fix: pr change

---------

Co-authored-by: sidwebworks <xodeveloper@gmail.com>
2025-08-03 01:10:23 +05:30
Daniel Hougaard
899d01237c docs(agent-injector): ldap auth method 2025-08-02 19:43:27 +04:00
Scott Wilson
ff5dbe74fd Merge pull request #4284 from Infisical/simplify-email-design
improvement(email-templates): simplify email design, refactor link/button to re-usable components and improve design
2025-08-01 18:48:53 -07:00
x032205
24004084f2 Merge pull request #4292 from Infisical/ENG-3422
feat(app-connections): GitHub Enterprise Server support
2025-08-01 21:45:05 -04:00
x032205
0e401ece73 Attempt to use octokit request from dependencies 2025-08-01 21:30:32 -04:00
x032205
c4e1651df7 consistent versioning 2025-08-01 21:19:03 -04:00
x032205
514c7596db Swap away from octokit request 2025-08-01 21:08:15 -04:00
Scott Wilson
9fbdede82c improvements: address feedback 2025-08-01 17:01:51 -07:00
x032205
e519637e89 Fix lint 2025-08-01 18:35:25 -04:00
x032205
ba393b0498 fix dropdown value issue 2025-08-01 18:29:26 -04:00
x032205
4150f81d83 Merge pull request #4282 from JuliusMieliauskas/fix-san-extension-contents
FIX: x509 SAN Extension to accept IPs and URLs as args
2025-08-01 15:24:22 -04:00
Sid
a45bba8537 feat: audit log disable storage flag (#4295)
* feat: audit log disable storage flag

* fix: pr changes

* fix: revert license fns

* Update frontend/src/layouts/OrganizationLayout/components/AuditLogBanner/AuditLogBanner.tsx
2025-08-02 00:29:53 +05:30
x032205
fe7e8e7240 Fix auth baseUrl for octokit 2025-08-01 13:49:38 -04:00
x032205
cf54365022 Update DALs to include gatewayId 2025-08-01 13:47:36 -04:00
Scott Wilson
4f26365c21 improvements: adjust secret search padding when no clear icon and fix access approval reviewer tooltips 2025-07-31 19:58:26 -07:00
x032205
c974df104e Improve types 2025-07-31 20:28:02 -04:00
x032205
e88fdc957e feat(app-connections): GitHub Enterprise Server support 2025-07-31 20:20:24 -04:00
Julius Mieliauskas
de2c1c5560 removed TLD requirement from SAN extension dns field 2025-07-31 23:51:07 +03:00
Julius Mieliauskas
2cbd66e804 changed url validation to use zod 2025-07-31 19:17:08 +03:00
Scott Wilson
4a55ecbe12 improvement: simplify email design, refactor link/button to re-usable components and improve design 2025-07-30 18:14:35 -07:00
Julius Mieliauskas
1e29d550be Fix x509 SAN Extension to accept IPs and URLs as args 2025-07-31 02:41:38 +03:00
62 changed files with 855 additions and 575 deletions

View File

@@ -38,6 +38,7 @@
"@octokit/core": "^5.2.1",
"@octokit/plugin-paginate-graphql": "^4.0.1",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/request": "8.4.1",
"@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1",
"@octopusdeploy/api-client": "^3.4.1",
@@ -9777,18 +9778,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
@@ -9835,11 +9824,6 @@
"node": "14 || >=16.14"
}
},
"node_modules/@octokit/auth-app/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-oauth-app": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
@@ -9855,18 +9839,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
@@ -9905,11 +9877,6 @@
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-oauth-device": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
@@ -9924,18 +9891,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
@@ -9974,11 +9929,6 @@
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-oauth-user": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
@@ -9994,18 +9944,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
@@ -10044,11 +9982,6 @@
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
@@ -10102,32 +10035,38 @@
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/core/node_modules/universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"license": "ISC"
},
"node_modules/@octokit/endpoint": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"version": "10.1.4",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz",
"integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
"@octokit/types": "^14.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"version": "25.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
"license": "MIT"
},
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^24.2.0"
"@octokit/openapi-types": "^25.1.0"
}
},
"node_modules/@octokit/graphql": {
@@ -10159,6 +10098,12 @@
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/graphql/node_modules/universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"license": "ISC"
},
"node_modules/@octokit/oauth-authorization-url": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
@@ -10181,18 +10126,6 @@
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
@@ -10231,11 +10164,6 @@
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/openapi-types": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
@@ -10376,31 +10304,54 @@
}
},
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/request/node_modules/@octokit/endpoint": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
"license": "MIT",
"dependencies": {
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
"version": "24.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
"license": "MIT"
},
"node_modules/@octokit/request/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"version": "13.10.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
"license": "MIT",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
"@octokit/openapi-types": "^24.2.0"
}
},
"node_modules/@octokit/request/node_modules/universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"license": "ISC"
},
"node_modules/@octokit/rest": {
"version": "20.0.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
@@ -18288,7 +18239,8 @@
"node_modules/fast-content-type-parse": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz",
"integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ=="
"integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==",
"license": "MIT"
},
"node_modules/fast-copy": {
"version": "3.0.1",
@@ -24776,6 +24728,12 @@
"jsonwebtoken": "^9.0.2"
}
},
"node_modules/octokit-auth-probot/node_modules/universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
"license": "ISC"
},
"node_modules/odbc": {
"version": "2.4.9",
"resolved": "https://registry.npmjs.org/odbc/-/odbc-2.4.9.tgz",
@@ -30705,9 +30663,10 @@
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
},
"node_modules/universal-user-agent": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
"license": "ISC"
},
"node_modules/universalify": {
"version": "2.0.1",

View File

@@ -158,6 +158,7 @@
"@octokit/core": "^5.2.1",
"@octokit/plugin-paginate-graphql": "^4.0.1",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/request": "8.4.1",
"@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1",
"@octopusdeploy/api-client": "^3.4.1",

View File

@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Reminder, "fromDate"))) {
await knex.schema.alterTable(TableName.Reminder, (t) => {
t.timestamp("fromDate", { useTz: true }).nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Reminder, "fromDate")) {
await knex.schema.alterTable(TableName.Reminder, (t) => {
t.dropColumn("fromDate");
});
}
}

View File

@@ -14,7 +14,8 @@ export const RemindersSchema = z.object({
repeatDays: z.number().nullable().optional(),
nextReminderDate: z.date(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
fromDate: z.date().nullable().optional()
});
export type TReminders = z.infer<typeof RemindersSchema>;

View File

@@ -1,8 +1,10 @@
// weird commonjs-related error in the CI requires us to do the import like this
import knex from "knex";
import { v4 as uuidv4 } from "uuid";
import { TDbClient } from "@app/db";
import { TableName, TAuditLogs } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
import { logger } from "@app/lib/logger";
@@ -150,43 +152,70 @@ export const auditLogDALFactory = (db: TDbClient) => {
// delete all audit log that have expired
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
const runPrune = async (dbClient: knex.Knex) => {
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
const today = new Date();
let deletedAuditLogIds: { id: string }[] = [];
let numberOfRetryOnFailure = 0;
let isRetrying = false;
const today = new Date();
let deletedAuditLogIds: { id: string }[] = [];
let numberOfRetryOnFailure = 0;
let isRetrying = false;
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
do {
try {
const findExpiredLogSubQuery = (tx || db)(TableName.AuditLog)
.where("expiresAt", "<", today)
.where("createdAt", "<", today) // to use audit log partition
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
.select("id")
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
do {
try {
const findExpiredLogSubQuery = dbClient(TableName.AuditLog)
.where("expiresAt", "<", today)
.where("createdAt", "<", today) // to use audit log partition
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
.select("id")
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
// eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
.whereIn("id", findExpiredLogSubQuery)
.del()
.returning("id");
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
logger.error(error, "Failed to delete audit log on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
// eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await dbClient(TableName.AuditLog)
.whereIn("id", findExpiredLogSubQuery)
.del()
.returning("id");
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
logger.error(error, "Failed to delete audit log on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
};
if (tx) {
await runPrune(tx);
} else {
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
await db.transaction(async (trx) => {
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
await runPrune(trx);
});
}
};
return { ...auditLogOrm, pruneAuditLog, find };
const create: TAuditLogDALFactory["create"] = async (tx) => {
const config = getConfig();
if (config.DISABLE_AUDIT_LOG_STORAGE) {
return {
...tx,
id: uuidv4(),
createdAt: new Date(),
updatedAt: new Date()
};
}
return auditLogOrm.create(tx);
};
return { ...auditLogOrm, create, pruneAuditLog, find };
};

View File

@@ -49,6 +49,7 @@ const baseSecretScanningDataSourceQuery = ({
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
db
@@ -82,6 +83,7 @@ const expandSecretScanningDataSource = <
connectionUpdatedAt,
connectionVersion,
connectionIsPlatformManagedCredentials,
connectionGatewayId,
...el
} = dataSource;
@@ -100,7 +102,8 @@ const expandSecretScanningDataSource = <
createdAt: connectionCreatedAt,
updatedAt: connectionUpdatedAt,
version: connectionVersion,
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
gatewayId: connectionGatewayId
}
: undefined
};

View File

@@ -59,6 +59,7 @@ const envSchema = z
AUDIT_LOGS_DB_ROOT_CERT: zpStr(
z.string().describe("Postgres database base64-encoded CA cert for Audit logs").optional()
),
DISABLE_AUDIT_LOG_STORAGE: zodStrBool.default("false").optional().describe("Disable audit log storage"),
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
@@ -482,6 +483,15 @@ export const overwriteSchema: {
fields: { key: keyof TEnvConfig; description?: string }[];
};
} = {
auditLogs: {
name: "Audit Logs",
fields: [
{
key: "DISABLE_AUDIT_LOG_STORAGE",
description: "Disable audit log storage"
}
]
},
aws: {
name: "AWS",
fields: [

View File

@@ -2144,7 +2144,8 @@ export const registerRoutes = async (
inviteOnlySignup: z.boolean().optional(),
redisConfigured: z.boolean().optional(),
secretScanningConfigured: z.boolean().optional(),
samlDefaultOrgSlug: z.string().optional()
samlDefaultOrgSlug: z.string().optional(),
auditLogStorageDisabled: z.boolean().optional()
})
}
},
@@ -2171,7 +2172,8 @@ export const registerRoutes = async (
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
redisConfigured: cfg.isRedisConfigured,
secretScanningConfigured: cfg.isSecretScanningConfigured,
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug,
auditLogStorageDisabled: Boolean(cfg.DISABLE_AUDIT_LOG_STORAGE)
};
}
});

View File

@@ -22,6 +22,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
message: z.string().trim().max(1024).optional(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
fromDate: z.string().datetime().nullable().optional(),
recipients: z.string().array().optional()
})
.refine((data) => {
@@ -45,6 +46,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
fromDate: req.body.fromDate,
recipients: req.body.recipients
}
});

View File

@@ -1,4 +1,5 @@
import { createAppAuth } from "@octokit/auth-app";
import { request } from "@octokit/request";
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import https from "https";
import RE2 from "re2";
@@ -12,7 +13,6 @@ import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { logger } from "@app/lib/logger";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { AppConnection } from "../app-connection-enums";
import { GitHubConnectionMethod } from "./github-connection-enums";
@@ -30,6 +30,23 @@ export const getGitHubConnectionListItem = () => {
};
};
export const getGitHubInstanceApiUrl = async (config: {
credentials: Pick<TGitHubConnectionConfig["credentials"], "host" | "instanceType">;
}) => {
const host = config.credentials.host || "github.com";
await blockLocalAndPrivateIpAddresses(host);
let apiBase: string;
if (config.credentials.instanceType === "server") {
apiBase = `${host}/api/v3`;
} else {
apiBase = `api.${host}`;
}
return apiBase;
};
export const requestWithGitHubGateway = async <T>(
appConnection: { gatewayId?: string | null },
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
@@ -73,7 +90,10 @@ export const requestWithGitHubGateway = async <T>(
return await httpRequest.request(finalRequestConfig);
} catch (error) {
const axiosError = error as AxiosError;
logger.error("Error during GitHub gateway request:", axiosError.message, axiosError.response?.data);
logger.error(
{ message: axiosError.message, data: axiosError.response?.data },
"Error during GitHub gateway request:"
);
throw error;
}
},
@@ -112,7 +132,10 @@ export const getGitHubAppAuthToken = async (appConnection: TGitHubConnection) =>
const appAuth = createAppAuth({
appId,
privateKey: appPrivateKey,
installationId: appConnection.credentials.installationId
installationId: appConnection.credentials.installationId,
request: request.defaults({
baseUrl: `https://${await getGitHubInstanceApiUrl(appConnection)}`
})
});
const { token } = await appAuth({ type: "installation" });
@@ -141,7 +164,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
const token =
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
let url: string | null = `https://api.${credentials.host || "github.com"}${path}`;
let url: string | null = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
let results: T[] = [];
let i = 0;
@@ -325,6 +348,8 @@ export const validateGitHubConnectionCredentials = async (
});
}
} catch (e: unknown) {
logger.error(e, "Unable to verify GitHub connection");
if (e instanceof BadRequestError) {
throw e;
}
@@ -355,7 +380,7 @@ export const validateGitHubConnectionCredentials = async (
};
}[];
}>(config, gatewayService, {
url: IntegrationUrls.GITHUB_USER_INSTALLATIONS.replace("api.github.com", `api.${host}`),
url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`,
headers: {
Accept: "application/json",
Authorization: `Bearer ${tokenResp.data.access_token}`,
@@ -377,11 +402,15 @@ export const validateGitHubConnectionCredentials = async (
switch (method) {
case GitHubConnectionMethod.App:
return {
installationId: credentials.installationId
installationId: credentials.installationId,
instanceType: credentials.instanceType,
host: credentials.host
};
case GitHubConnectionMethod.OAuth:
return {
accessToken: tokenResp.data.access_token
accessToken: tokenResp.data.access_token,
instanceType: credentials.instanceType,
host: credentials.host
};
default:
throw new InternalServerError({

View File

@@ -10,26 +10,59 @@ import {
import { GitHubConnectionMethod } from "./github-connection-enums";
export const GitHubConnectionOAuthInputCredentialsSchema = z.object({
code: z.string().trim().min(1, "OAuth code required"),
host: z.string().trim().optional()
});
export const GitHubConnectionOAuthInputCredentialsSchema = z.union([
z.object({
code: z.string().trim().min(1, "OAuth code required"),
instanceType: z.literal("server"),
host: z.string().trim().min(1, "Host is required for server instance type")
}),
z.object({
code: z.string().trim().min(1, "OAuth code required"),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const GitHubConnectionAppInputCredentialsSchema = z.object({
code: z.string().trim().min(1, "GitHub App code required"),
installationId: z.string().min(1, "GitHub App Installation ID required"),
host: z.string().trim().optional()
});
export const GitHubConnectionAppInputCredentialsSchema = z.union([
z.object({
code: z.string().trim().min(1, "GitHub App code required"),
installationId: z.string().min(1, "GitHub App Installation ID required"),
instanceType: z.literal("server"),
host: z.string().trim().min(1, "Host is required for server instance type")
}),
z.object({
code: z.string().trim().min(1, "GitHub App code required"),
installationId: z.string().min(1, "GitHub App Installation ID required"),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const GitHubConnectionOAuthOutputCredentialsSchema = z.object({
accessToken: z.string(),
host: z.string().trim().optional()
});
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
z.object({
accessToken: z.string(),
instanceType: z.literal("server"),
host: z.string().trim().min(1)
}),
z.object({
accessToken: z.string(),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const GitHubConnectionAppOutputCredentialsSchema = z.object({
installationId: z.string(),
host: z.string().trim().optional()
});
export const GitHubConnectionAppOutputCredentialsSchema = z.union([
z.object({
installationId: z.string(),
instanceType: z.literal("server"),
host: z.string().trim().min(1)
}),
z.object({
installationId: z.string(),
instanceType: z.literal("cloud").optional(),
host: z.string().trim().optional()
})
]);
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
@@ -84,11 +117,17 @@ export const GitHubConnectionSchema = z.intersection(
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
BaseGitHubConnectionSchema.extend({
method: z.literal(GitHubConnectionMethod.App),
credentials: GitHubConnectionAppOutputCredentialsSchema.pick({})
credentials: z.object({
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
host: z.string().optional()
})
}),
BaseGitHubConnectionSchema.extend({
method: z.literal(GitHubConnectionMethod.OAuth),
credentials: GitHubConnectionOAuthOutputCredentialsSchema.pick({})
credentials: z.object({
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
host: z.string().optional()
})
})
]);

View File

@@ -15,10 +15,15 @@ export const validateAltNameField = z
.trim()
.refine(
(name) => {
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
return (
isFQDN(name, { allow_wildcard: true, require_tld: false }) ||
z.string().url().safeParse(name).success ||
z.string().email().safeParse(name).success ||
isValidIp(name)
);
},
{
message: "SAN must be a valid hostname, email address, or IP address"
message: "SAN must be a valid hostname, email address, IP address or URL"
}
);
@@ -39,10 +44,15 @@ export const validateAltNamesField = z
if (data === "") return true;
// Split and validate each alt name
return data.split(", ").every((name) => {
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
return (
isFQDN(name, { allow_wildcard: true, require_tld: false }) ||
z.string().url().safeParse(name).success ||
z.string().email().safeParse(name).success ||
isValidIp(name)
);
});
},
{
message: "Each alt name must be a valid hostname or email address"
message: "Each alt name must be a valid hostname, email address, IP address or URL"
}
);

View File

@@ -152,7 +152,7 @@ export const InternalCertificateAuthorityFns = ({
extensions.push(extendedKeyUsagesExtension);
}
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = [];
if (subscriber.subjectAlternativeNames?.length) {
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
@@ -160,10 +160,18 @@ export const InternalCertificateAuthorityFns = ({
return { type: "email", value: altName };
}
if (isFQDN(altName, { allow_wildcard: true })) {
if (isFQDN(altName, { allow_wildcard: true, require_tld: false })) {
return { type: "dns", value: altName };
}
if (z.string().url().safeParse(altName).success) {
return { type: "url", value: altName };
}
if (z.string().ip().safeParse(altName).success) {
return { type: "ip", value: altName };
}
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
});
@@ -418,7 +426,7 @@ export const InternalCertificateAuthorityFns = ({
);
}
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = [];
if (altNames) {
altNamesArray = altNames.split(",").map((altName) => {
@@ -426,10 +434,18 @@ export const InternalCertificateAuthorityFns = ({
return { type: "email", value: altName };
}
if (isFQDN(altName, { allow_wildcard: true })) {
if (isFQDN(altName, { allow_wildcard: true, require_tld: false })) {
return { type: "dns", value: altName };
}
if (z.string().url().safeParse(altName).success) {
return { type: "url", value: altName };
}
if (z.string().ip().safeParse(altName).success) {
return { type: "ip", value: altName };
}
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
});

View File

@@ -79,25 +79,33 @@ export const reminderServiceFactory = ({
repeatDays,
nextReminderDate: nextReminderDateInput,
recipients,
projectId
projectId,
fromDate: fromDateInput
}: {
secretId?: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
fromDate?: string | null;
projectId: string;
}) => {
if (!secretId) {
throw new BadRequestError({ message: "secretId is required" });
}
let nextReminderDate;
let fromDate;
if (nextReminderDateInput) {
nextReminderDate = new Date(nextReminderDateInput);
}
if (repeatDays && repeatDays > 0) {
nextReminderDate = $addDays(repeatDays);
if (repeatDays) {
if (fromDateInput) {
fromDate = new Date(fromDateInput);
nextReminderDate = fromDate;
} else {
nextReminderDate = $addDays(repeatDays);
}
}
if (!nextReminderDate) {
@@ -112,7 +120,8 @@ export const reminderServiceFactory = ({
await reminderDAL.updateById(existingReminder.id, {
message,
repeatDays,
nextReminderDate
nextReminderDate,
fromDate
});
reminderId = existingReminder.id;
} else {
@@ -121,7 +130,8 @@ export const reminderServiceFactory = ({
secretId,
message,
repeatDays,
nextReminderDate
nextReminderDate,
fromDate
});
reminderId = newReminder.id;
}
@@ -280,14 +290,28 @@ export const reminderServiceFactory = ({
}
const processedReminders = remindersData.map(
({ secretId, message, repeatDays, nextReminderDate: nextReminderDateInput, recipients, projectId }) => {
({
secretId,
message,
repeatDays,
nextReminderDate: nextReminderDateInput,
recipients,
projectId,
fromDate: fromDateInput
}) => {
let nextReminderDate;
let fromDate;
if (nextReminderDateInput) {
nextReminderDate = new Date(nextReminderDateInput);
}
if (repeatDays && repeatDays > 0 && !nextReminderDate) {
nextReminderDate = $addDays(repeatDays);
if (repeatDays && !nextReminderDate) {
if (fromDateInput) {
fromDate = new Date(fromDateInput);
nextReminderDate = fromDate;
} else {
nextReminderDate = $addDays(repeatDays);
}
}
if (!nextReminderDate) {
@@ -302,17 +326,19 @@ export const reminderServiceFactory = ({
repeatDays,
nextReminderDate,
recipients: recipients ? [...new Set(recipients)] : [],
projectId
projectId,
fromDate
};
}
);
const newReminders = await reminderDAL.insertMany(
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate }) => ({
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate, fromDate }) => ({
secretId,
message,
repeatDays,
nextReminderDate
nextReminderDate,
fromDate
})),
tx
);

View File

@@ -8,6 +8,7 @@ export type TReminder = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate: Date;
fromDate?: Date | null;
createdAt: Date;
updatedAt: Date;
};
@@ -21,6 +22,7 @@ export type TCreateReminderDTO = {
secretId?: string;
message?: string | null;
repeatDays?: number | null;
fromDate?: string | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
};
@@ -31,6 +33,7 @@ export type TBatchCreateReminderDTO = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | Date | null;
fromDate?: Date | null;
recipients?: string[] | null;
projectId?: string;
}[];
@@ -95,6 +98,7 @@ export interface TReminderServiceFactory {
nextReminderDate?: string | null;
recipients?: string[] | null;
projectId: string;
fromDate?: string | null;
}) => Promise<{
id: string;
created: boolean;

View File

@@ -3,6 +3,7 @@ import sodium from "libsodium-wrappers";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import {
getGitHubAppAuthToken,
getGitHubInstanceApiUrl,
GitHubConnectionMethod,
makePaginatedGitHubRequest,
requestWithGitHubGateway
@@ -73,7 +74,7 @@ const getPublicKey = async (
}
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
method: "GET",
headers: {
Accept: "application/vnd.github+json",
@@ -111,7 +112,7 @@ const deleteSecret = async (
}
await requestWithGitHubGateway(connection, gatewayService, {
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
method: "DELETE",
headers: {
Accept: "application/vnd.github+json",
@@ -157,7 +158,7 @@ const putSecret = async (
}
await requestWithGitHubGateway(connection, gatewayService, {
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
method: "PUT",
headers: {
Accept: "application/vnd.github+json",

View File

@@ -30,6 +30,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
db
@@ -65,6 +66,7 @@ const expandSecretSync = (
connectionUpdatedAt,
connectionVersion,
connectionIsPlatformManagedCredentials,
connectionGatewayId,
...el
} = secretSync;
@@ -83,7 +85,8 @@ const expandSecretSync = (
createdAt: connectionCreatedAt,
updatedAt: connectionUpdatedAt,
version: connectionVersion,
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
gatewayId: connectionGatewayId
},
folder: folder
? {

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface AccessApprovalRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
projectName: string;
@@ -38,18 +40,15 @@ export const AccessApprovalRequestTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
You have a new access approval request pending review for the project <strong>{projectName}</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
<strong>{requesterFullName}</strong> (
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
{requesterEmail}
</Link>
) has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
<strong>{requesterFullName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>)
has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
<strong>{environment}</strong> environment.
</Text>
{isTemporary && (
<Text className="text-[14px] text-red-500 leading-[24px]">
<Text className="text-[14px] text-red-600 leading-[24px]">
<strong>This access will expire {expiresIn} after approval.</strong>
</Text>
)}
@@ -67,13 +66,8 @@ export const AccessApprovalRequestTemplate = ({
</Text>
)}
</Section>
<Section className="text-center mt-[28px]">
<Button
href={approvalUrl}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Review Request
</Button>
<Section className="text-center">
<BaseButton href={approvalUrl}>Review Request</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -0,0 +1,18 @@
import { Button } from "@react-email/components";
import React from "react";
type Props = {
href: string;
children: string;
};
export const BaseButton = ({ href, children }: Props) => {
return (
<Button
href={href}
className="rounded-[8px] py-[12px] px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
{children}
</Button>
);
};

View File

@@ -16,23 +16,21 @@ export const BaseEmailWrapper = ({ title, preview, children, siteUrl }: BaseEmai
<Body className="bg-gray-300 my-auto mx-auto font-sans px-[8px] py-[4px]">
<Preview>{preview}</Preview>
<Container className="bg-white rounded-xl my-[40px] mx-auto pb-[0px] max-w-[500px]">
<Section className="border-0 border-b border-[#d1e309] border-solid bg-[#EBF852] mb-[44px] h-[10px] rounded-t-xl" />
<Section className="px-[32px] mb-[18px]">
<Section className="w-[48px] h-[48px] border border-solid border-gray-300 rounded-full bg-gray-100 mx-auto">
<Img
src={`https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75`}
width="32"
alt="Infisical Logo"
className="mx-auto"
/>
</Section>
<Section className="mb-[24px] px-[24px] mt-[24px]">
<Img
src="https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75"
width="36"
alt="Infisical Logo"
className="mx-auto"
/>
</Section>
<Hr className=" mb-[32px] mt-[0px] h-[1px]" />
<Section className="px-[28px]">{children}</Section>
<Hr className=" mt-[32px] mb-[0px] h-[1px]" />
<Section className="px-[24px] text-center">
<Text className="text-gray-500 text-[12px]">
Email sent via{" "}
<Link href={siteUrl} className="text-slate-700 no-underline">
<Link href={siteUrl} className="text-slate-700 underline decoration-slate-700">
Infisical
</Link>
</Text>

View File

@@ -0,0 +1,15 @@
import { Link } from "@react-email/components";
import React from "react";
type Props = {
href: string;
children: string;
};
export const BaseLink = ({ href, children }: Props) => {
return (
<Link href={href} className="text-slate-700 underline decoration-slate-700">
{children}
</Link>
);
};

View File

@@ -1,7 +1,8 @@
import { Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface EmailMfaTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
code: string;
@@ -25,11 +26,7 @@ export const EmailMfaTemplate = ({ code, siteUrl, isCloud }: EmailMfaTemplatePro
<strong>Not you?</strong>{" "}
{isCloud ? (
<>
Contact us at{" "}
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>{" "}
immediately
Contact us at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink> immediately
</>
) : (
"Contact your administrator immediately"

View File

@@ -1,7 +1,8 @@
import { Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface EmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
code: string;
@@ -29,10 +30,7 @@ export const EmailVerificationTemplate = ({ code, siteUrl, isCloud }: EmailVerif
<strong>Questions about Infisical?</strong>{" "}
{isCloud ? (
<>
Email us at{" "}
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>
Email us at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
</>
) : (
"Contact your administrator"

View File

@@ -1,7 +1,8 @@
import { Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface ExternalImportFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
error: string;
@@ -21,12 +22,9 @@ export const ExternalImportFailedTemplate = ({ error, siteUrl, provider }: Exter
</Text>
<Text className="text-black text-[14px] leading-[24px]">
If your issue persists, you can contact the Infisical team at{" "}
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>
.
<BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>.
</Text>
<Text className="text-[14px] text-red-500 leading-[24px]">
<Text className="text-[14px] text-red-600 leading-[24px]">
<strong>Error:</strong> "{error}"
</Text>
</Section>

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface IntegrationSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -30,7 +31,7 @@ export const IntegrationSyncFailedTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>{count}</strong> integration(s) failed to sync
</Heading>
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<strong>Project</strong>
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
<strong>Environment</strong>
@@ -38,15 +39,10 @@ export const IntegrationSyncFailedTemplate = ({
<strong>Secret Path</strong>
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
<strong className="text-black">Failure Reason:</strong>
<Text className="text-[14px] mt-[4px] text-red-500 leading-[24px]">"{syncMessage}"</Text>
<Text className="text-[14px] mt-[4px] text-red-600 leading-[24px]">"{syncMessage}"</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={integrationUrl}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
View Integrations
</Button>
<Section className="text-center">
<BaseButton href={integrationUrl}>View Integrations</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,7 +1,8 @@
import { Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface NewDeviceLoginTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
email: string;
@@ -42,9 +43,7 @@ export const NewDeviceLoginTemplate = ({
<Text className="mb-[0px]">
If you believe that this login is suspicious, please contact{" "}
{isCloud ? (
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>
<BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
) : (
"your administrator"
)}{" "}

View File

@@ -1,7 +1,8 @@
import { Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface OrgAdminBreakglassAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
email: string;
@@ -35,10 +36,7 @@ export const OrgAdminBreakglassAccessTemplate = ({
<Text className="text-[14px] mt-[4px]">{userAgent}</Text>
<Text className="text-[14px]">
If you'd like to disable Admin SSO Bypass, please visit{" "}
<Link href={`${siteUrl}/organization/settings`} className="text-slate-700 no-underline">
Organization Security Settings
</Link>
.
<BaseLink href={`${siteUrl}/organization/settings`}>Organization Security Settings</BaseLink>.
</Text>
</Section>
</BaseEmailWrapper>

View File

@@ -2,6 +2,7 @@ import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface OrgAdminProjectGrantAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview"> {
email: string;
@@ -24,8 +25,8 @@ export const OrgAdminProjectGrantAccessTemplate = ({
</Heading>
<Section className="px-[24px] mt-[36px] pt-[24px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px] mt-[4px]">
The organization admin <strong>{email}</strong> has self-issued direct access to the project{" "}
<strong>{projectName}</strong>.
The organization admin <BaseLink href={`mailto:${email}`}>{email}</BaseLink> has self-issued direct access to
the project <strong>{projectName}</strong>.
</Text>
</Section>
</BaseEmailWrapper>

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface OrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
metadata?: string;
@@ -36,15 +38,13 @@ export const OrganizationInvitationTemplate = ({
<br />
<strong>{organizationName}</strong> on <strong>Infisical</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
{inviterFirstName && inviterUsername ? (
<>
<strong>{inviterFirstName}</strong> (
<Link href={`mailto:${inviterUsername}`} className="text-slate-700 no-underline">
{inviterUsername}
</Link>
) has invited you to collaborate on <strong>{organizationName}</strong>.
<BaseLink href={`mailto:${inviterUsername}`}>{inviterUsername}</BaseLink>) has invited you to collaborate
on <strong>{organizationName}</strong>.
</>
) : (
<>
@@ -53,13 +53,12 @@ export const OrganizationInvitationTemplate = ({
)}
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
<Section className="text-center">
<BaseButton
href={`${callback_url}?token=${token}${metadata ? `&metadata=${metadata}` : ""}&to=${encodeURIComponent(email)}&organization_id=${organizationId}`}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Accept Invite
</Button>
</BaseButton>
</Section>
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
<Text className="mb-[0px]">

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface PasswordResetTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
email: string;
@@ -20,16 +22,13 @@ export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, t
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>Account Recovery</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">A password reset was requested for your Infisical account.</Text>
<Text className="text-[14px]">
If you did not initiate this request, please contact{" "}
{isCloud ? (
<>
us immediately at{" "}
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>
us immediately at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
</>
) : (
"your administrator immediately"
@@ -37,13 +36,8 @@ export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, t
.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Reset Password
</Button>
<Section className="text-center">
<BaseButton href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}>Reset Password</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,8 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Button, Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseLink } from "@app/services/smtp/emails/BaseLink";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface PasswordSetupTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -16,19 +18,16 @@ export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, t
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>Password Setup</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">Someone requested to set up a password for your Infisical account.</Text>
<Text className="text-[14px] text-red-500">
<Text className="text-[14px] text-red-600">
Make sure you are already logged in to Infisical in the current browser before clicking the link below.
</Text>
<Text className="text-[14px]">
If you did not initiate this request, please contact{" "}
{isCloud ? (
<>
us immediately at{" "}
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>
us immediately at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
</>
) : (
"your administrator immediately"
@@ -36,7 +35,7 @@ export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, t
.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Section className="text-center">
<Button
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface ProjectAccessRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
projectName: string;
@@ -30,26 +32,17 @@ export const ProjectAccessRequestTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
A user has requested access to the project <strong>{projectName}</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
<strong>{requesterName}</strong> (
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
{requesterEmail}
</Link>
) has requested access to the project <strong>{projectName}</strong> in the organization{" "}
<strong>{orgName}</strong>.
<strong>{requesterName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>) has
requested access to the project <strong>{projectName}</strong> in the organization <strong>{orgName}</strong>.
</Text>
<Text className="text-[14px] text-slate-700 leading-[24px]">
<strong className="text-black">User note:</strong> "{note}"
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={callback_url}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Grant Access
</Button>
<Section className="text-center">
<BaseButton href={callback_url}>Grant Access</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface ProjectInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
@@ -18,18 +19,13 @@ export const ProjectInvitationTemplate = ({ callback_url, workspaceName, siteUrl
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
You've been invited to join a project on Infisical
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
You've been invited to join the project <strong>{workspaceName}</strong>.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={callback_url}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Join Project
</Button>
<Section className="text-center">
<BaseButton href={callback_url}>Join Project</BaseButton>
</Section>
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
<Text className="mb-[0px]">

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface ScimUserProvisionedTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
@@ -24,18 +25,13 @@ export const ScimUserProvisionedTemplate = ({
<br />
<strong>{organizationName}</strong> on <strong>Infisical</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
You've been invited to collaborate on <strong>{organizationName}</strong>.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={callback_url}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Accept Invite
</Button>
<Section className="text-center">
<BaseButton href={callback_url}>Accept Invite</BaseButton>
</Section>
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
<Text className="mb-[0px]">

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface SecretApprovalRequestBypassedTemplateProps
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -35,13 +37,10 @@ export const SecretApprovalRequestBypassedTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
A secret approval request has been bypassed in the project <strong>{projectName}</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-black text-[14px] leading-[24px]">
<strong>{requesterFullName}</strong> (
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
{requesterEmail}
</Link>
) has {requestType === "change" ? "merged" : "accessed"} a secret {requestType === "change" ? "to" : "in"}{" "}
<strong>{requesterFullName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>)
has {requestType === "change" ? "merged" : "accessed"} a secret {requestType === "change" ? "to" : "in"}{" "}
<strong>{secretPath}</strong> in the <strong>{environment}</strong> environment without obtaining the required
approval.
</Text>
@@ -50,13 +49,8 @@ export const SecretApprovalRequestBypassedTemplate = ({
{bypassReason}"
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={approvalUrl}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Review Bypass
</Button>
<Section className="text-center">
<BaseButton href={approvalUrl}>Review Bypass</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface SecretApprovalRequestNeedsReviewTemplateProps
@@ -27,20 +28,15 @@ export const SecretApprovalRequestNeedsReviewTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
A secret approval request for the project <strong>{projectName}</strong> requires review
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">Hello {firstName},</Text>
<Text className="text-black text-[14px] leading-[24px]">
You have a new secret change request pending your review for the project <strong>{projectName}</strong> in the
organization <strong>{organizationName}</strong>.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={approvalUrl}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Review Changes
</Button>
<Section className="text-center">
<BaseButton href={approvalUrl}>Review Changes</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface SecretLeakIncidentTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
numberOfSecrets: number;
@@ -24,7 +26,7 @@ export const SecretLeakIncidentTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
Infisical has uncovered <strong>{numberOfSecrets}</strong> secret(s) from a recent commit
</Heading>
<Section className="px-[24px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">
You are receiving this notification because one or more leaked secrets have been detected in a recent commit
{(pusher_email || pusher_name) && (
@@ -33,11 +35,7 @@ export const SecretLeakIncidentTemplate = ({
pushed by <strong>{pusher_name ?? "Unknown Pusher"}</strong>{" "}
{pusher_email && (
<>
(
<Link href={`mailto:${pusher_email}`} className="text-slate-700 no-underline">
{pusher_email}
</Link>
)
(<BaseLink href={`mailto:${pusher_email}`}>{pusher_email}</BaseLink>)
</>
)}
</>
@@ -49,24 +47,16 @@ export const SecretLeakIncidentTemplate = ({
a comment in the given programming language. This will prevent future notifications from being sent out for
these secrets.
</Text>
<Text className="text-[14px] text-red-500">
<Text className="text-[14px] text-red-600">
If these are production secrets, please rotate them immediately.
</Text>
<Text className="text-[14px]">
Once you have taken action, be sure to update the status of the risk in the{" "}
<Link href={`${siteUrl}/organization/secret-scanning`} className="text-slate-700 no-underline">
Infisical Dashboard
</Link>
.
<BaseLink href={`${siteUrl}/organization/secret-scanning`}>Infisical Dashboard</BaseLink>.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={`${siteUrl}/organization/secret-scanning`}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
View Leaked Secrets
</Button>
<Section className="text-center">
<BaseButton href={`${siteUrl}/organization/secret-scanning`}>View Leaked Secrets</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface SecretRequestCompletedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -20,7 +21,7 @@ export const SecretRequestCompletedTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>A secret has been shared with you</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] text-center pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] text-center pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">
{respondentUsername ? <strong>{respondentUsername}</strong> : "Someone"} shared a secret{" "}
{name && (
@@ -31,13 +32,8 @@ export const SecretRequestCompletedTemplate = ({
with you.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={secretRequestUrl}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
View Secret
</Button>
<Section className="text-center">
<BaseButton href={secretRequestUrl}>View Secret</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface SecretRotationFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -28,7 +29,7 @@ export const SecretRotationFailedTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
Your <strong>{rotationType}</strong> rotation <strong>{rotationName}</strong> failed to rotate
</Heading>
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<strong>Name</strong>
<Text className="text-[14px] mt-[4px]">{rotationName}</Text>
<strong>Type</strong>
@@ -40,15 +41,12 @@ export const SecretRotationFailedTemplate = ({
<strong>Secret Path</strong>
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
<strong>Reason:</strong>
<Text className="text-[14px] text-red-500 mt-[4px]">{content}</Text>
<Text className="text-[14px] text-red-600 mt-[4px]">{content}</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={`${rotationUrl}?search=${rotationName}&secretPath=${secretPath}`}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
<Section className="text-center">
<BaseButton href={`${rotationUrl}?search=${rotationName}&secretPath=${secretPath}`}>
View in Infisical
</Button>
</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface SecretScanningScanFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -30,7 +31,7 @@ export const SecretScanningScanFailedTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
Infisical encountered an error while attempting to scan the resource <strong>{resourceName}</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<strong>Resource</strong>
<Text className="text-[14px] mt-[4px]">{resourceName}</Text>
<strong>Data Source</strong>
@@ -40,15 +41,10 @@ export const SecretScanningScanFailedTemplate = ({
<strong>Timestamp</strong>
<Text className="text-[14px] mt-[4px]">{timestamp}</Text>
<strong>Error</strong>
<Text className="text-[14px] text-red-500 mt-[4px]">{errorMessage}</Text>
<Text className="text-[14px] text-red-600 mt-[4px]">{errorMessage}</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={url}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
View in Infisical
</Button>
<Section className="text-center">
<BaseButton href={url}>View in Infisical</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,7 +1,9 @@
import { Button, Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface SecretScanningSecretsDetectedTemplateProps
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -32,7 +34,7 @@ export const SecretScanningSecretsDetectedTemplate = ({
Infisical has uncovered <strong>{numberOfSecrets}</strong> secret(s)
{isDiffScan ? " from a recent commit to" : " in"} <strong>{resourceName}</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[8px] pb-[8px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">
You are receiving this notification because one or more leaked secrets have been detected
{isDiffScan && " in a recent commit"}
@@ -43,11 +45,7 @@ export const SecretScanningSecretsDetectedTemplate = ({
pushed by <strong>{authorName ?? "Unknown Pusher"}</strong>{" "}
{authorEmail && (
<>
(
<Link href={`mailto:${authorEmail}`} className="text-slate-700 no-underline">
{authorEmail}
</Link>
)
(<BaseLink href={`mailto:${authorEmail}`}>{authorEmail}</BaseLink>)
</>
)}
</>
@@ -65,24 +63,16 @@ export const SecretScanningSecretsDetectedTemplate = ({
a comment in the given programming language. This will prevent future notifications from being sent out for
these secrets.
</Text>
<Text className="text-[14px] text-red-500">
<Text className="text-[14px] text-red-600">
If these are production secrets, please rotate them immediately.
</Text>
<Text className="text-[14px]">
Once you have taken action, be sure to update the finding status in the{" "}
<Link href={url} className="text-slate-700 no-underline">
Infisical Dashboard
</Link>
.
<BaseLink href={url}>Infisical Dashboard</BaseLink>.
</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={url}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
View Leaked Secrets
</Button>
<Section className="text-center">
<BaseButton href={url}>View Leaked Secrets</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface SecretSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -28,7 +29,7 @@ export const SecretSyncFailedTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
Your <strong>{syncDestination}</strong> sync <strong>{syncName}</strong> failed to complete
</Heading>
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
<strong>Name</strong>
<Text className="text-[14px] mt-[4px]">{syncName}</Text>
<strong>Destination</strong>
@@ -50,17 +51,12 @@ export const SecretSyncFailedTemplate = ({
{failureMessage && (
<>
<strong>Reason:</strong>
<Text className="text-[14px] text-red-500 mt-[4px]">{failureMessage}</Text>
<Text className="text-[14px] text-red-600 mt-[4px]">{failureMessage}</Text>
</>
)}
</Section>
<Section className="text-center mt-[28px]">
<Button
href={syncUrl}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
View in Infisical
</Button>
<Section className="text-center">
<BaseButton href={syncUrl}>View in Infisical</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface ServiceTokenExpiryNoticeTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -24,20 +25,15 @@ export const ServiceTokenExpiryNoticeTemplate = ({
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>Service token expiry notice</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">
Your service token <strong>{tokenName}</strong> for the project <strong>{projectName}</strong> will expire
within 24 hours.
</Text>
<Text>If this token is still needed for your workflow, please create a new one before it expires.</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={url}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Create New Token
</Button>
<Section className="text-center">
<BaseButton href={url}>Create New Token</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -1,7 +1,8 @@
import { Heading, Link, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
import { BaseLink } from "./BaseLink";
interface SignupEmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
code: string;
@@ -29,10 +30,7 @@ export const SignupEmailVerificationTemplate = ({ code, siteUrl, isCloud }: Sign
<strong>Questions about setting up Infisical?</strong>{" "}
{isCloud ? (
<>
Email us at{" "}
<Link href="mailto:support@infisical.com" className="text-slate-700 no-underline">
support@infisical.com
</Link>
Email us at <BaseLink href="mailto:support@infisical.com">support@infisical.com</BaseLink>
</>
) : (
"Contact your administrator"

View File

@@ -1,6 +1,7 @@
import { Button, Heading, Section, Text } from "@react-email/components";
import { Heading, Section, Text } from "@react-email/components";
import React from "react";
import { BaseButton } from "./BaseButton";
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
interface UnlockAccountTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
@@ -18,19 +19,14 @@ export const UnlockAccountTemplate = ({ token, siteUrl, callback_url }: UnlockAc
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
<strong>Unlock your Infisical account</strong>
</Heading>
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
<Text className="text-[14px]">
Your account has been temporarily locked due to multiple failed login attempts.
</Text>
<Text>If these attempts were not made by you, reset your password immediately.</Text>
</Section>
<Section className="text-center mt-[28px]">
<Button
href={`${callback_url}?token=${token}`}
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
>
Unlock Account
</Button>
<Section className="text-center">
<BaseButton href={`${callback_url}?token=${token}`}>Unlock Account</BaseButton>
</Section>
</BaseEmailWrapper>
);

View File

@@ -105,44 +105,93 @@ The templates hold an array of templates that will be rendered and injected into
### Authentication
The Infisical Agent Injector only supports Machine Identity [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) authentication at the moment.
The Infisical Agent Injector supports Machine Identity [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) and [LDAP Auth](/documentation/platform/identities/ldap-auth) authentication.
To configure Kubernetes Auth, you need to set the `auth.type` field to `kubernetes` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication.
```yaml
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
```
<AccordionGroup>
<Accordion title="Kubernetes Auth">
### Example ConfigMap
```yaml config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config-map
data:
config.yaml: |
infisical:
address: "https://app.infisical.com"
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
templates:
- destination-path: "/path/to/save/secrets/file.txt"
template-content: |
{{- with secret "<your-project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
To configure Kubernetes Auth, you need to set the `auth.type` field to `kubernetes` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication.
```yaml
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
```
```bash
kubectl apply -f config-map.yaml
```
### Example ConfigMap
```yaml config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config-map
data:
config.yaml: |
infisical:
address: "https://app.infisical.com"
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
templates:
- destination-path: "/path/to/save/secrets/file.txt"
template-content: |
{{- with secret "<your-project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash
kubectl apply -f config-map.yaml
```
</Accordion>
<Accordion title="LDAP Auth">
To configure LDAP Auth, you need to set the `auth.type` field to `ldap-auth` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication. Configure the `auth.config.username` and `auth.config.password` to the username and password of the LDAP user to authenticate with.
```yaml
auth:
type: "ldap-auth"
config:
identity-id: "<your-infisical-machine-identity-id>"
username: "<your-ldap-username>"
password: "<your-ldap-password>"
```
### Example ConfigMap
```yaml config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config-map
data:
config.yaml: |
infisical:
address: "https://app.infisical.com"
auth:
type: "ldap-auth"
config:
identity-id: "<your-infisical-machine-identity-id>"
username: "<your-ldap-username>"
password: "<your-ldap-password>"
templates:
- destination-path: "/path/to/save/secrets/file.txt"
template-content: |
{{- with secret "<your-project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash
kubectl apply -f config-map.yaml
```
</Accordion>
</AccordionGroup>
To use the config map in your pod, you will need to add the `org.infisical.com/agent-config-map` annotation to your pod's deployment. The value of the annotation is the name of the config map you created above.
```yaml

View File

@@ -65,7 +65,14 @@ Example values:
default="false"
optional
>
Determines whether your Infisical instance can automatically read the service account token of the pod it's running on. Used for features such as the IRSA auth method.
Determines whether your Infisical instance can automatically read the service
account token of the pod it's running on. Used for features such as the IRSA
auth method.
</ParamField>
<ParamField query="DISABLE_AUDIT_LOG_STORAGE" type="string" default="false">
Disable storing audit logs in the database. This is useful if you're using
audit log streams and don't want to store them in the database.
</ParamField>
## CORS

View File

@@ -35,6 +35,7 @@ export type TServerConfig = {
initialized: boolean;
allowSignUp: boolean;
allowedSignUpDomain?: string | null;
disableAuditLogStorage: boolean;
isMigrationModeOn?: boolean;
trustSamlEmails: boolean;
trustLdapEmails: boolean;

View File

@@ -11,6 +11,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
method: GitHubConnectionMethod.OAuth;
credentials: {
code: string;
instanceType?: "cloud" | "server";
host?: string;
};
}
@@ -19,6 +20,7 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
credentials: {
code: string;
installationId: string;
instanceType?: "cloud" | "server";
host?: string;
};
}

View File

@@ -12,14 +12,15 @@ export const useCreateReminder = (secretId: string) => {
const queryClient = useQueryClient();
return useMutation<Reminder, object, CreateReminderDTO>({
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => {
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients, fromDate }) => {
const { data } = await apiRequest.post<{ reminder: Reminder }>(
`/api/v1/reminders/secrets/${secretId}`,
{
message,
repeatDays,
nextReminderDate,
recipients
recipients,
fromDate
}
);
return data.reminder;

View File

@@ -2,6 +2,7 @@ export type CreateReminderDTO = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: Date | null;
fromDate?: Date | null;
secretId: string;
recipients?: string[];
};

View File

@@ -5,4 +5,5 @@ export type ServerStatus = {
secretScanningConfigured: boolean;
redisConfigured: boolean;
samlDefaultOrgSlug: string;
auditLogStorageDisabled: boolean;
};

View File

@@ -6,10 +6,11 @@ import { twMerge } from "tailwind-merge";
import { CreateOrgModal } from "@app/components/organization/CreateOrgModal";
import { Banner } from "@app/components/page-frames/Banner";
import { useServerConfig } from "@app/context";
import { useServerConfig, useSubscription } from "@app/context";
import { usePopUp } from "@app/hooks";
import { useFetchServerStatus } from "@app/hooks/api";
import { AuditLogBanner } from "./components/AuditLogBanner";
import { InsecureConnectionBanner } from "./components/InsecureConnectionBanner";
import { Navbar } from "./components/NavBar";
import { OrgSidebar } from "./components/OrgSidebar";
@@ -31,6 +32,7 @@ export const OrganizationLayout = () => {
const containerHeight = config.pageFrameContent ? "h-[94vh]" : "h-screen";
const { data: serverDetails, isLoading } = useFetchServerStatus();
const { subscription } = useSubscription();
return (
<>
@@ -41,6 +43,7 @@ export const OrganizationLayout = () => {
<Navbar />
{!isLoading && !serverDetails?.redisConfigured && <RedisBanner />}
{!isLoading && !serverDetails?.emailConfigured && <SmtpBanner />}
{!isLoading && subscription.auditLogs && <AuditLogBanner />}
{!window.isSecureContext && <InsecureConnectionBanner />}
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row">
<OrgSidebar isHidden={isInsideProject} />

View File

@@ -0,0 +1,23 @@
import { useOrganization } from "@app/context";
import { useFetchServerStatus, useGetAuditLogStreams } from "@app/hooks/api";
import { OrgAlertBanner } from "../OrgAlertBanner";
export const AuditLogBanner = () => {
const org = useOrganization();
const { data: status, isLoading: isLoadingStatus } = useFetchServerStatus();
const { data: streams, isLoading: isLoadingStreams } = useGetAuditLogStreams(org.currentOrg.id);
if (isLoadingStreams || isLoadingStatus || !streams) return null;
if (status?.auditLogStorageDisabled && !streams.length) {
return (
<OrgAlertBanner
text="Attention: Audit logs storage is disabled but no audit log streams have been configured."
link="https://infisical.com/docs/documentation/platform/audit-log-streams/audit-log-streams"
/>
);
}
return null;
};

View File

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

View File

@@ -48,9 +48,16 @@ const formSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.GitHub),
method: z.nativeEnum(GitHubConnectionMethod),
credentials: z
.object({
host: z.string().optional()
})
.union([
z.object({
instanceType: z.literal("cloud").optional(),
host: z.string().optional()
}),
z.object({
instanceType: z.literal("server"),
host: z.string().min(1, "Required")
})
])
.optional()
});
@@ -70,7 +77,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
defaultValues: appConnection ?? {
app: AppConnection.GitHub,
method: GitHubConnectionMethod.App,
gatewayId: null
gatewayId: null,
credentials: {
instanceType: "cloud"
}
}
});
@@ -78,6 +88,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
handleSubmit,
control,
watch,
setValue,
formState: { isSubmitting, isDirty }
} = form;
@@ -85,6 +96,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
const selectedMethod = watch("method");
const instanceType = watch("credentials.instanceType");
const onSubmit = (formData: FormData) => {
setIsRedirecting(true);
@@ -103,7 +115,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
switch (formData.method) {
case GitHubConnectionMethod.App:
window.location.assign(
`${githubHost}/apps/${appClientSlug}/installations/new?state=${state}`
`${githubHost}/${formData.credentials?.instanceType === "server" ? "github-apps" : "apps"}/${appClientSlug}/installations/new?state=${state}`
);
break;
case GitHubConnectionMethod.OAuth:
@@ -175,13 +187,54 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
</FormControl>
)}
/>
{subscription.gateway && (
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
<div className="order-1 ml-3">GitHub Enterprise Options</div>
</AccordionTrigger>
<AccordionContent childrenClassName="px-0">
<Accordion type="single" collapsible className="w-full">
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
<div className="order-1 ml-3">GitHub Enterprise Options</div>
</AccordionTrigger>
<AccordionContent childrenClassName="px-0">
<Controller
name="credentials.instanceType"
control={control}
render={({ field }) => (
<FormControl label="Instance Type">
<Select
value={field.value}
onValueChange={(e) => {
field.onChange(e);
if (e === "cloud") {
setValue("gatewayId", null);
}
}}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
placeholder="Enterprise Cloud"
position="popper"
>
<SelectItem value="cloud">Enterprise Cloud</SelectItem>
<SelectItem value="server">Enterprise Server</SelectItem>
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.host"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Instance Hostname"
isOptional={instanceType === "cloud"}
isRequired={instanceType === "server"}
>
<Input {...field} placeholder="github.com" />
</FormControl>
)}
/>
{subscription.gateway && instanceType === "server" && (
<OrgPermissionCan
I={OrgGatewayPermissionActions.AttachGateways}
a={OrgPermissionSubjects.Gateway}
@@ -190,7 +243,6 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
<Controller
control={control}
name="gatewayId"
defaultValue=""
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
@@ -204,7 +256,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
<div>
<Select
isDisabled={!isAllowed}
value={value as string}
value={value || (null as unknown as string)}
onValueChange={onChange}
className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-none"
@@ -214,7 +266,7 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
>
<SelectItem
value={null as unknown as string}
onClick={() => onChange(undefined)}
onClick={() => onChange(null)}
>
Internet Gateway
</SelectItem>
@@ -231,25 +283,10 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
/>
)}
</OrgPermissionCan>
<Controller
name="credentials.host"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Hostname"
isOptional
>
<Input {...field} placeholder="github.com" />
</FormControl>
)}
/>
</AccordionContent>
</AccordionItem>
</Accordion>
)}
)}
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="mt-8 flex items-center">
<Button
className="mr-4"

View File

@@ -408,6 +408,7 @@ export const OAuthCallbackPage = () => {
credentials: {
code: code as string,
installationId: installationId as string,
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
...(credentials?.host && { host: credentials.host })
},
gatewayId
@@ -416,6 +417,7 @@ export const OAuthCallbackPage = () => {
connectionId,
credentials: {
code: code as string,
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
...(credentials?.host && { host: credentials.host })
},
gatewayId
@@ -431,6 +433,7 @@ export const OAuthCallbackPage = () => {
method: GitHubConnectionMethod.App,
credentials: {
code: code as string,
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
installationId: installationId as string,
...(credentials?.host && { host: credentials.host })
},
@@ -440,6 +443,7 @@ export const OAuthCallbackPage = () => {
method: GitHubConnectionMethod.OAuth,
credentials: {
code: code as string,
...(credentials?.instanceType && { instanceType: credentials.instanceType }),
...(credentials?.host && { host: credentials.host })
},
gatewayId

View File

@@ -12,6 +12,7 @@ export const AuditLogsPage = () => {
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Helmet>
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
<div className="w-full max-w-7xl">
<PageHeader

View File

@@ -1,5 +1,5 @@
import { Fragment } from "react";
import { faFile } from "@fortawesome/free-solid-svg-icons";
import { faCancel, faFile } from "@fortawesome/free-solid-svg-icons";
import { twMerge } from "tailwind-merge";
import {
@@ -16,7 +16,7 @@ import {
Tr
} from "@app/components/v2";
import { Timezone } from "@app/helpers/datetime";
import { useGetAuditLogs } from "@app/hooks/api";
import { useFetchServerStatus, useGetAuditLogs } from "@app/hooks/api";
import { TGetAuditLogsFilter } from "@app/hooks/api/auditLogs/types";
import { LogsTableRow } from "./LogsTableRow";
@@ -30,6 +30,8 @@ type Props = {
const AUDIT_LOG_LIMIT = 30;
export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
const { data: status } = useFetchServerStatus();
// Determine the project ID for filtering
const filterProjectId =
// Use the projectId from the filter if it exists
@@ -79,7 +81,11 @@ export const LogsTable = ({ filter, refetchInterval, timezone }: Props) => {
{isEmpty && (
<Tr>
<Td colSpan={3}>
<EmptyState title="No audit logs on file" icon={faFile} />
{status?.auditLogStorageDisabled ? (
<EmptyState title="Audit log storage is disabled" icon={faCancel} />
) : (
<EmptyState title="No audit logs on file" icon={faFile} />
)}
</Td>
</Tr>
)}

View File

@@ -52,8 +52,15 @@ export const SecretSearchInput = ({
if (activeIndex === 0 && e.key === "Enter") setIsOpen(true);
}}
autoComplete="off"
className="input text-md h-[2.3rem] w-full rounded-md rounded-l-none bg-mineshaft-800 py-[0.375rem] pl-2.5 pr-8 text-gray-400 placeholder-mineshaft-50 placeholder-opacity-50 outline-none duration-200 placeholder:text-sm hover:ring-bunker-400/60 focus:bg-mineshaft-700/80 focus:ring-1 focus:ring-primary-400/50"
placeholder="Search by secret, folder, tag or metadata..."
className={twMerge(
"input text-md h-[2.3rem] w-full rounded-md rounded-l-none bg-mineshaft-800 py-[0.375rem] pl-2.5 text-gray-400 placeholder-mineshaft-50 placeholder-opacity-50 outline-none duration-200 placeholder:text-sm hover:ring-bunker-400/60 focus:bg-mineshaft-700/80 focus:ring-1 focus:ring-primary-400/50",
hasSearch ? "pr-8" : "pr-2.5"
)}
placeholder={
isSingleEnv
? "Search by secret, folder, tag or metadata..."
: "Search by secret or folder name..."
}
value={value}
onChange={(e) => onChange(e.target.value)}
/>

View File

@@ -538,16 +538,20 @@ export const SecretApprovalRequestChanges = ({
className="flex flex-nowrap items-center justify-between space-x-2 rounded border border-mineshaft-600 bg-mineshaft-800 px-2 py-1"
key={`required-approver-${requiredApprover.userId}`}
>
<div className="flex text-sm">
<Tooltip
content={`${requiredApprover.firstName || ""} ${
requiredApprover.lastName || ""
}`}
>
<span>{requiredApprover?.email}</span>
</Tooltip>
<span className="text-red">*</span>
</div>
<Tooltip
content={
requiredApprover.firstName
? `${requiredApprover.firstName || ""} ${requiredApprover.lastName || ""}`
: undefined
}
position="left"
sideOffset={10}
>
<div className="flex text-sm">
<div>{requiredApprover?.email}</div>
<span className="text-red">*</span>
</div>
</Tooltip>
<div>
{reviewer?.comment && (
<Tooltip className="max-w-lg break-words" content={reviewer.comment}>

View File

@@ -4,6 +4,7 @@ import { faClock, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import { twMerge } from "tailwind-merge";
import { z } from "zod";
@@ -33,6 +34,7 @@ const MIN_REPEAT_DAYS = 1;
const MAX_REPEAT_DAYS = 365;
const DEFAULT_REPEAT_DAYS = 30;
const DEFAULT_TEXTAREA_ROWS = 8;
const ONE_DAY_IN_MILLIS = 86400000;
// Enums
enum ReminderType {
@@ -79,6 +81,11 @@ const ReminderFormSchema = z.object({
.refine((data) => data > new Date(), { message: "Reminder date must be in the future" })
.nullable()
.optional(),
fromDate: z.coerce
.date()
.refine((data) => data > new Date(), { message: "From date must be in the future" })
.nullable()
.optional(),
reminderType: z.enum(["Recurring", "One Time"])
});
@@ -86,7 +93,7 @@ export type TReminderFormSchema = z.infer<typeof ReminderFormSchema>;
// Custom hook for form state management
const useReminderForm = (reminderData?: Reminder) => {
const { repeatDays, message, nextReminderDate } = reminderData || {};
const { repeatDays, message, nextReminderDate, fromDate } = reminderData || {};
const isEditMode = Boolean(reminderData);
@@ -96,9 +103,10 @@ const useReminderForm = (reminderData?: Reminder) => {
message: message || "",
nextReminderDate: nextReminderDate || null,
reminderType: repeatDays ? ReminderType.Recurring : ReminderType.OneTime,
recipients: []
recipients: [],
fromDate
}),
[repeatDays, message, nextReminderDate]
[repeatDays, message, nextReminderDate, fromDate]
);
return {
@@ -153,7 +161,8 @@ export const CreateReminderForm = ({
message: reminderData?.message || "",
nextReminderDate: reminderData?.nextReminderDate || null,
reminderType: reminderData?.repeatDays ? ReminderType.Recurring : ReminderType.OneTime,
recipients: []
recipients: [],
fromDate: reminderData?.fromDate
},
resolver: zodResolver(ReminderFormSchema)
});
@@ -170,6 +179,7 @@ export const CreateReminderForm = ({
// Watch form values
const reminderType = watch("reminderType");
const fromDate = watch("fromDate");
// Invalidate queries helper
const invalidateQueries = () => {
@@ -195,7 +205,8 @@ export const CreateReminderForm = ({
message: data.message,
recipients: data.recipients?.map((r) => r.value) || [],
secretId,
nextReminderDate: data.nextReminderDate
nextReminderDate: data.nextReminderDate,
fromDate: data.fromDate
});
invalidateQueries();
@@ -243,6 +254,7 @@ export const CreateReminderForm = ({
if (newType === ReminderType.Recurring) {
setValue("repeatDays", DEFAULT_REPEAT_DAYS);
setValue("nextReminderDate", null);
setValue("fromDate", null);
} else if (newType === ReminderType.OneTime) {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
@@ -259,6 +271,7 @@ export const CreateReminderForm = ({
const {
repeatDays: repeatDaysInitial,
fromDate: fromDateInitial,
message,
recipients,
nextReminderDate: nextReminderDateInitial
@@ -266,6 +279,7 @@ export const CreateReminderForm = ({
if (repeatDaysInitial) {
setValue("repeatDays", repeatDaysInitial);
setValue("fromDate", fromDateInitial);
setValue("reminderType", ReminderType.Recurring);
} else {
setValue("reminderType", ReminderType.OneTime);
@@ -323,44 +337,73 @@ export const CreateReminderForm = ({
{/* Conditional Fields Based on Reminder Type */}
{reminderType === ReminderType.Recurring ? (
<Controller
control={control}
name="repeatDays"
render={({ field, fieldState }) => (
<div>
<div className="grid grid-cols-[1fr,auto] gap-x-2">
<Controller
control={control}
name="repeatDays"
render={({ field, fieldState }) => (
<div>
<FormControl
isRequired
className="mb-0"
label="Reminder Interval (in days)"
isError={Boolean(fieldState.error)}
errorText={fieldState.error?.message || ""}
>
<Input
onChange={(el) => {
const value = parseInt(el.target.value, 10);
setValue("repeatDays", Number.isNaN(value) ? null : value);
}}
type="number"
placeholder={DEFAULT_REPEAT_DAYS.toString()}
value={field.value || ""}
min={MIN_REPEAT_DAYS}
max={MAX_REPEAT_DAYS}
/>
</FormControl>
{/* Interval description */}
<div
className={twMerge(
"ml-1 mt-2 text-xs",
field.value ? "opacity-60" : "opacity-0"
)}
>
A reminder will be sent every{" "}
{field.value && field.value > 1 ? `${field.value} days` : "day"}
{fromDate ? ` starting from ${format(fromDate, "MM/dd/yy")}` : ""}
</div>
</div>
)}
/>
<Controller
control={control}
name="fromDate"
render={({ field, fieldState }) => (
<FormControl
isRequired
className="mb-0"
label="Reminder Interval (in days)"
label="Start Date"
tooltipText="When enabled, this date will be used as the start date for the first reminder"
isError={Boolean(fieldState.error)}
errorText={fieldState.error?.message || ""}
>
<Input
onChange={(el) => {
const value = parseInt(el.target.value, 10);
setValue("repeatDays", Number.isNaN(value) ? null : value);
<DatePicker
value={field.value || undefined}
className="w-full"
onChange={field.onChange}
dateFormat="P"
popUpProps={{
open: isDatePickerOpen,
onOpenChange: setIsDatePickerOpen
}}
type="number"
placeholder={DEFAULT_REPEAT_DAYS.toString()}
value={field.value || ""}
min={MIN_REPEAT_DAYS}
max={MAX_REPEAT_DAYS}
popUpContentProps={{}}
hideTime
hidden={{ before: new Date(Date.now() + ONE_DAY_IN_MILLIS) }}
/>
</FormControl>
{/* Interval description */}
<div
className={twMerge(
"ml-1 mt-2 text-xs",
field.value ? "opacity-60" : "opacity-0"
)}
>
A reminder will be sent every{" "}
{field.value && field.value > 1 ? `${field.value} days` : "day"}
</div>
</div>
)}
/>
)}
/>
</div>
) : (
<Controller
control={control}
@@ -385,7 +428,7 @@ export const CreateReminderForm = ({
}}
popUpContentProps={{}}
hideTime
hidden={{ before: new Date(Date.now() + 86400000) }}
hidden={{ before: new Date(Date.now() + ONE_DAY_IN_MILLIS) }}
/>
</FormControl>
</div>