1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-20 22:32:28 +00:00

Compare commits

..

115 Commits

Author SHA1 Message Date
bd233ebe9b allow git branch mapping to env 2023-02-23 00:00:56 -05:00
f92269f2ec Merge pull request from Grraahaam/feat/pr-template
feat(docs): added a base pull request template
2023-02-23 11:16:19 +07:00
2143db5eb5 Update messaging 2023-02-22 17:21:44 -08:00
0c72f50b5e Updated readme's 2023-02-22 16:29:53 -08:00
3c4c616242 update 2fa prompt in cli 2023-02-22 18:52:09 -05:00
153baad49f Merge pull request from ImBIOS/docs/translation-indonesia
translate: add Bahasa Indonesia for README
2023-02-22 15:22:17 -08:00
75a2ab636c Merge pull request from akhilmhdh/feat/table-loader
Table loading state and empty states
2023-02-22 14:57:23 -08:00
05a77e612c Minor style updates 2023-02-22 14:54:59 -08:00
d02bc06dce Merge pull request from alexdanilowicz/patch-2
docs: fix nit typos in README
2023-02-22 14:54:53 -05:00
e1f88f1a7b docs: fix nit typos in README
Fix 'development' typo in sub header of README and do not hyphenate use-cases.
2023-02-22 11:51:41 -08:00
86a2647134 feat(ui): added table skeleton and loading for settings page,
fix(ui): resolved missing loading state in add new member and whitespace in project settings page
2023-02-22 23:29:26 +05:30
621b640af4 feat(ui): added new components empty state and skeleton 2023-02-22 23:27:36 +05:30
af64582efd Merge pull request from Infisical/socket-labs-smtp
Add support and docs for SocketLabs email SMTP
2023-02-22 15:47:53 +07:00
6ad70f24a2 Add support and docs for SocketLabs email SMTP 2023-02-22 15:44:47 +07:00
89bc9a823c translate: add Bahasa Indonesia for README 2023-02-22 12:03:57 +07:00
40250b7ecf Merge pull request from Infisical/ip-alerts
New device (IP and user agent) login detection and alert
2023-02-21 18:11:16 +07:00
2d6d32923d Finish alert for new device login detection 2023-02-21 18:01:26 +07:00
7cb6aee3f7 Add docs for MFA 2023-02-21 16:08:09 +07:00
469d042f4b Add CircleCI docs 2023-02-21 13:07:17 +07:00
c38ccdb915 Merge pull request from Infisical/mfa
MFA
2023-02-21 12:47:56 +07:00
baaa92427f Remove dependency cycle 2023-02-21 12:43:17 +07:00
1ff2c61b3a Remove storage of protected key 2023-02-21 12:31:19 +07:00
0b356e0e83 Update README.md 2023-02-20 21:14:50 -08:00
eb55c053eb Merge pull request from umrak11/feature/improve-backup-pdf-generation
Refactored and improved PDF backup generation
2023-02-20 20:56:15 -08:00
07b307e4b1 Merge pull request from esau-morais/issue-318
Add missing `forgot-password` in pt-BR and save selected env on URL
2023-02-20 20:44:27 -08:00
5bee6a5e24 Merge branch 'issue-318' of https://github.com/esau-morais/infisical into issue-318 2023-02-20 20:41:00 -08:00
bdc99e34cc Fix TS issue 2023-02-20 20:40:32 -08:00
cee10fb507 Merge branch 'main' into issue-318 2023-02-20 20:36:44 -08:00
74e78bb967 Merge remote-tracking branch 'origin' into mfa 2023-02-21 11:31:17 +07:00
ea5811c24c Fixed the bug with the tab indicators 2023-02-20 20:30:03 -08:00
d31b7ae4af Merge pull request from animeshdas2000/fix/undefined-url
Fix: After trying to delete the last remaining project, it keeps loading and returns undefined in the URL
2023-02-20 19:54:41 -08:00
75eac1b972 Add LoginSRPDetail to v2 auth route 2023-02-21 10:52:31 +07:00
c65ce14de3 Merge branch 'main' into fix/undefined-url 2023-02-20 19:49:04 -08:00
f8c4ccd64c Updated readme 2023-02-20 19:17:04 -08:00
43ce222725 Minor style updates 2023-02-20 18:12:29 -08:00
c7ebeecb6b Merge pull request from akhilmhdh/feat/new-org-settings
Revamped the org settings page
2023-02-20 17:25:27 -08:00
243c6ca22e feat: changed delete org membership to v2 2023-02-20 23:22:34 +05:30
66f1c57a2a feat(ui): completed org settings page revamp 2023-02-20 23:22:34 +05:30
c0d1495761 feat(ui): api hooks for new org settings page 2023-02-20 23:22:34 +05:30
e5f6ed3dc7 feat(ui): components changes for new org settings page 2023-02-20 23:22:32 +05:30
ab62d91b09 Refactored and improved PDF backup generation 2023-02-20 10:12:38 +01:00
59beabb445 Fix change password private key removal 2023-02-20 13:27:16 +07:00
d5bc377e3d Added notifications to 2FA and fixed state 2023-02-19 20:11:24 -08:00
2bdb20f42f Merge pull request from Infisical/mfa
MFA
2023-02-19 18:33:11 -05:00
0062df58a2 Fixed dashboard for the corner case of purely personal secrets 2023-02-19 12:02:30 -08:00
b6bbfc08ad increase helm chart 2023-02-19 12:52:28 -05:00
5baccc73c9 Add mongo root password 2023-02-19 12:52:28 -05:00
20e7eae4fe Add more Accept-Encoding to integrations syncs 2023-02-19 13:42:37 +07:00
8432f71d58 add secrets auto reload 2023-02-19 00:44:38 -05:00
604c22d64d Merge remote-tracking branch 'origin' into mfa 2023-02-19 12:41:23 +07:00
c1deb08df8 always pull gamma iamge 2023-02-19 00:08:07 -05:00
66f201746f update gamma values 2023-02-19 00:06:38 -05:00
1c61ffbd36 patch upload script 2023-02-19 00:00:58 -05:00
e5ba8eb281 fix upload script 2023-02-18 23:50:35 -05:00
f542e07c33 download dependency for helm chart in upload step 2023-02-18 23:40:10 -05:00
1082d7f869 update helm docs for self install 2023-02-18 23:28:52 -05:00
4a3adaa347 Begin in-memory privat key storage 2023-02-19 10:54:54 +07:00
1659dab87d Merge pull request from Grraahaam/feat/helm-mongodb-persistence
feat(chart): mongodb persistence
2023-02-18 18:06:39 -05:00
d88599714f update helm chart to refect dep update 2023-02-18 11:33:39 -05:00
71bf56a2b7 Update k8 operator dependencies 2023-02-18 11:32:46 -05:00
0fba78ad16 Update http net package 2023-02-18 10:16:27 -05:00
92560f5e1f Merge remote-tracking branch 'origin' into mfa 2023-02-18 16:49:51 +07:00
0d484b93eb Merge pull request from Infisical/smoothen-integrations
Smoothen integrations
2023-02-18 16:30:56 +07:00
5f3b8c55b8 Merge branch 'main' into smoothen-integrations 2023-02-18 16:27:58 +07:00
553416689c Fix merge conflicts with main 2023-02-18 16:23:05 +07:00
b0744fd21d Wired frontend to use the batch stucture 2023-02-18 00:02:52 -08:00
be38844a5b Add 2FA in CLI 2023-02-17 20:48:19 -05:00
54e2b661bc ran prettier to fix indentation 2023-02-17 14:20:58 +05:30
b81d8eba25 added notification to .env errors 2023-02-16 21:05:38 -08:00
dbcd2b0988 Patch undefined req.secrets 2023-02-17 11:56:06 +07:00
1d11f11eaf Refactor login cmd 2023-02-16 15:49:14 -08:00
f2d7401d1d Add support for version 2 auth 2023-02-16 15:47:59 -08:00
91cb9750b4 Merge branch 'main' into fix/undefined-url 2023-02-17 00:33:55 +05:30
3e0d4cb70a fixed infinite loading in dashboard 2023-02-17 00:31:00 +05:30
dab677b360 Merge branch 'smoothen-integrations' of https://github.com/Infisical/infisical into smoothen-integrations 2023-02-17 01:18:31 +07:00
625c0785b5 Add validation to batch secret endpoint 2023-02-17 01:12:13 +07:00
540a8b4201 Fixed the bug with updating tags 2023-02-16 09:53:33 -08:00
11f86da1f6 Fixed the bug with updating tags 2023-02-16 09:35:57 -08:00
ab5ffa9ee6 Updated billing in the settings tab 2023-02-16 08:13:49 -08:00
65bec23292 Begin rewiring frontend to use batch route for CRUD secret ops 2023-02-16 22:43:53 +07:00
635ae941d7 Merge branch 'main' of https://github.com/Infisical/infisical 2023-02-15 23:56:33 -08:00
a9753fb784 Update the text in start guide 2023-02-15 23:56:16 -08:00
b587d9b35a Update README.md 2023-02-15 23:03:51 -08:00
aa68bc05d9 Updated 'are you sure?' message 2023-02-15 13:46:13 -08:00
66566a401f Updated the infisical guide 2023-02-15 13:23:14 -08:00
5aa75ecd3f feat: save selected env on url 2023-02-15 12:46:54 -03:00
0a77f9a0c8 fix(i18n): add missing forgot-password in pt-BR 2023-02-15 11:06:48 -03:00
b5d4cfed03 chore(docs): chart documentation generation 2023-02-15 09:43:17 +01:00
c57394bdab Merge remote-tracking branch 'origin' into mfa 2023-02-15 11:44:42 +07:00
da857f321b allow to set default env in project file 2023-02-14 19:57:27 -08:00
754ea09400 Add simple tries left for MFA 2023-02-15 10:28:23 +07:00
f28a2ea151 Small nits 2023-02-14 18:40:02 -08:00
c7dd028771 Update README.md 2023-02-14 18:34:41 -08:00
3c94bacda9 Update README.md 2023-02-14 18:31:45 -08:00
8e85847de3 improve example .env file format 2023-02-14 17:33:24 -08:00
b710944630 Add more edge-cases to MFA 2023-02-15 01:29:40 +07:00
280f482fc8 Fix merge conflicts 2023-02-14 17:40:42 +07:00
e1ad8fbee8 Refactoring functions into services, helper functions, hooks, patch bugs 2023-02-14 17:38:58 +07:00
56ca6039ba fix(chart): backend service typos 2023-02-14 10:50:34 +01:00
d3fcb69c50 fix(chart): backend service missing configuration 2023-02-14 01:18:41 +01:00
2db4a29ad7 chore(docs): chart setup + chart release notes.txt 2023-02-14 01:18:41 +01:00
4df82a6ff1 feat(chart): mailhog for local development 2023-02-14 01:18:41 +01:00
cdf73043e1 fix(chart): mongodb custom users + docs 2023-02-14 01:18:41 +01:00
ca07d1c50e fix(chart): helpers template auth.password typo 2023-02-14 01:18:41 +01:00
868011479b feat(chart): mongodb persistence 2023-02-14 01:18:41 +01:00
13b1805d04 Checkpoint 2 2023-02-13 17:11:31 +07:00
30b2b85446 Fix merge conflicts 2023-02-13 15:24:19 +07:00
e53fd110f6 Checkpoint weaving frontend and backend MFA 2023-02-13 12:29:36 +07:00
17406e413d Merge remote-tracking branch 'origin/2fa' into mfa 2023-02-11 16:08:13 +07:00
9b219f67b0 Update package.json 2023-02-11 16:07:32 +07:00
669861d7a8 General frontend structure for 2FA - done 2023-02-09 15:49:47 -08:00
bb752863fa Fix merge conflicts 2023-01-30 19:48:12 +07:00
cf5603c8e3 Finish preliminary backwards-compatible transition from user encryption scheme v1 to v2 with argon2 and protected key 2023-01-30 19:38:13 +07:00
77b1011207 feat(docs): added a pull request template 2023-01-30 11:54:52 +01:00
5cadb9e2f9 Finish MFA v1 and refactor all tokens into separate TokenService with modified collection 2023-01-23 22:10:15 +07:00
227 changed files with 8486 additions and 3361 deletions
.github
README.md
backend/src
cli
docs
frontend
.eslintrc.js
.storybook
next.config.jspackage-lock.jsonpackage.json
public
src
components
config
const.ts
ee/components
helpers
hooks
layouts/AppLayout
AppLayout.tsx
components/NavBar
pages
reactQuery.ts
services
views/Settings
OrgSettingsPage
PersonalSettingsPage/SecuritySection
ProjectSettingsPage
ProjectSettingsPage.tsx
components
AutoCapitalizationSection
EnvironmentSection
SecretTagsSection
ServiceTokenSection
tailwind.config.js
helm-charts
i18n
img
k8-operator

22
.github/pull_request_template.md vendored Normal file

@ -0,0 +1,22 @@
# Description 📣
*Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.*
## Type ✨
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation
# Tests 🛠️
*Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. You may want to add screenshots when relevant and possible*
```sh
# Here's some code block to paste some code snippets
```
---
- [ ] I have read the [contributing guide](https://infisical.com/docs/contributing/overview), agreed and acknowledged the [code of conduct](https://infisical.com/docs/contributing/code-of-conduct). 📝

86
.github/values.yaml vendored

@ -1,11 +1,5 @@
#####
# INFISICAL K8 DEFAULT VALUES FILE
# PLEASE REPLACE VALUES/EDIT AS REQUIRED
#####
nameOverride: ""
frontend:
enabled: true
name: frontend
podAnnotations: {}
deploymentAnnotations:
@ -13,17 +7,18 @@ frontend:
replicaCount: 2
image:
repository: infisical/frontend
pullPolicy: Always
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-secret-frontend
service:
# type of the frontend service
type: ClusterIP
# define the nodePort if service type is NodePort
# nodePort:
annotations: {}
type: ClusterIP
nodePort: ""
frontendEnvironmentVariables: null
backend:
enabled: true
name: backend
podAnnotations: {}
deploymentAnnotations:
@ -31,63 +26,46 @@ backend:
replicaCount: 2
image:
repository: infisical/backend
pullPolicy: Always
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-backend-secret
service:
annotations: {}
type: ClusterIP
nodePort: ""
backendEnvironmentVariables: null
## Mongo DB persistence
mongodb:
name: mongodb
podAnnotations: {}
image:
repository: mongo
pullPolicy: IfNotPresent
tag: "latest"
service:
annotations: {}
enabled: true
persistence:
enabled: false
# By default the backend will be connected to a Mongo instance in the cluster.
# However, it is recommended to add a managed document DB connection string because the DB instance in the cluster does not have persistence yet ( data will be deleted on next deploy).
# Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
mongodbConnection: {}
# externalMongoDBConnectionString: <>
## By default the backend will be connected to a Mongo instance within the cluster
## However, it is recommended to add a managed document DB connection string for production-use (DBaaS)
## Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
## e.g. "mongodb://<user>:<pass>@<host>:<port>/<database-name>"
mongodbConnection:
externalMongoDBConnectionString: ""
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
hostName: gamma.infisical.com # replace with your domain
frontend:
# cert-manager.io/issuer: letsencrypt-nginx
hostName: gamma.infisical.com ## <- Replace with your own domain
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls: []
tls:
[]
# - secretName: letsencrypt-nginx
# hosts:
# - infisical.local
## Complete Ingress example
# ingress:
# enabled: true
# annotations:
# kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx
# hostName: k8.infisical.com
# frontend:
# path: /
# pathType: Prefix
# backend:
# path: /api
# pathType: Prefix
# tls:
# - secretName: letsencrypt-nginx
# hosts:
# - k8.infisical.com
###
### YOU MUST FILL IN ALL SECRETS BELOW
###
backendEnvironmentVariables: {}
frontendEnvironmentVariables: {}
mailhog:
enabled: false

File diff suppressed because one or more lines are too long

@ -1,7 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { patchRouterParam } = require('./utils/patchAsyncRoutes');
import express, { Request, Response } from 'express';
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import cookieParser from 'cookie-parser';
@ -42,6 +42,8 @@ import {
integrationAuth as v1IntegrationAuthRouter
} from './routes/v1';
import {
signup as v2SignupRouter,
auth as v2AuthRouter,
users as v2UsersRouter,
organizations as v2OrganizationsRouter,
workspace as v2WorkspaceRouter,
@ -110,6 +112,8 @@ app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
// v2 routes
app.use('/api/v2/signup', v2SignupRouter);
app.use('/api/v2/auth', v2AuthRouter);
app.use('/api/v2/users', v2UsersRouter);
app.use('/api/v2/organizations', v2OrganizationsRouter);
app.use('/api/v2/workspace', v2EnvironmentRouter);

@ -5,6 +5,8 @@ const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!;
const SALT_ROUNDS = parseInt(process.env.SALT_ROUNDS!) || 10;
const JWT_AUTH_LIFETIME = process.env.JWT_AUTH_LIFETIME! || '10d';
const JWT_AUTH_SECRET = process.env.JWT_AUTH_SECRET!;
const JWT_MFA_LIFETIME = process.env.JWT_MFA_LIFETIME! || '5m';
const JWT_MFA_SECRET = process.env.JWT_MFA_SECRET!;
const JWT_REFRESH_LIFETIME = process.env.JWT_REFRESH_LIFETIME! || '90d';
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
const JWT_SERVICE_SECRET = process.env.JWT_SERVICE_SECRET!;
@ -56,6 +58,8 @@ export {
SALT_ROUNDS,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_MFA_LIFETIME,
JWT_MFA_SECRET,
JWT_REFRESH_LIFETIME,
JWT_REFRESH_SECRET,
JWT_SERVICE_SECRET,

@ -5,7 +5,8 @@ import * as Sentry from '@sentry/node';
import * as bigintConversion from 'bigint-conversion';
const jsrp = require('jsrp');
import { User, LoginSRPDetail } from '../../models';
import { createToken, issueTokens, clearTokens } from '../../helpers/auth';
import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth';
import { checkUserDevice } from '../../helpers/user';
import {
ACTION_LOGIN,
ACTION_LOGOUT
@ -111,7 +112,14 @@ export const login2 = async (req: Request, res: Response) => {
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// issue tokens
const tokens = await issueTokens({ userId: user._id.toString() });
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {

@ -1,14 +1,13 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, EMAIL_TOKEN_LIFETIME } from '../../config';
import { MembershipOrg, Organization, User, Token } from '../../models';
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
import { MembershipOrg, Organization, User } from '../../models';
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg';
import { checkEmailVerification } from '../../helpers/signup';
import { createToken } from '../../helpers/auth';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
import { sendMail } from '../../helpers/nodemailer';
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED } from '../../variables';
import { TokenService } from '../../services';
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables';
/**
* Delete organization membership with id [membershipOrgId] from organization
@ -163,18 +162,11 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
const organization = await Organization.findOne({ _id: organizationId });
if (organization) {
const token = crypto.randomBytes(16).toString('hex');
await Token.findOneAndUpdate(
{ email: inviteeEmail },
{
email: inviteeEmail,
token,
createdAt: new Date(),
ttl: Math.floor(+new Date() / 1000) + EMAIL_TOKEN_LIFETIME // time in seconds, i.e unix
},
{ upsert: true, new: true }
);
const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail,
organizationId: organization._id
});
await sendMail({
template: 'organizationInvitation.handlebars',
@ -226,10 +218,12 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
if (!membershipOrg)
throw new Error('Failed to find any invitations for email');
await checkEmailVerification({
await TokenService.validateToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email,
code
organizationId: membershipOrg.organization,
token: code
});
if (user && user?.publicKey) {

@ -1,14 +1,14 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require('jsrp');
import * as bigintConversion from 'bigint-conversion';
import { User, Token, BackupPrivateKey, LoginSRPDetail } from '../../models';
import { checkEmailVerification } from '../../helpers/signup';
import { User, BackupPrivateKey, LoginSRPDetail } from '../../models';
import { createToken } from '../../helpers/auth';
import { sendMail } from '../../helpers/nodemailer';
import { EMAIL_TOKEN_LIFETIME, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
import { TokenService } from '../../services';
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables';
import { BadRequestError } from '../../utils/errors';
/**
@ -31,20 +31,12 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
error: 'Failed to send email verification for password reset'
});
}
const token = crypto.randomBytes(16).toString('hex');
await Token.findOneAndUpdate(
{ email },
{
email,
token,
createdAt: new Date(),
ttl: Math.floor(+new Date() / 1000) + EMAIL_TOKEN_LIFETIME // time in seconds, i.e unix
},
{ upsert: true, new: true }
);
const token = await TokenService.createToken({
type: TOKEN_EMAIL_PASSWORD_RESET,
email
});
await sendMail({
template: 'passwordReset.handlebars',
subjectLine: 'Infisical password reset',
@ -55,7 +47,6 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
callback_url: SITE_URL + '/password-reset'
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@ -88,10 +79,11 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
error: 'Failed email verification for password reset'
});
}
await checkEmailVerification({
await TokenService.validateToken({
type: TOKEN_EMAIL_PASSWORD_RESET,
email,
code
token: code
});
// generate temporary password-reset token
@ -174,8 +166,18 @@ export const srp1 = async (req: Request, res: Response) => {
*/
export const changePassword = async (req: Request, res: Response) => {
try {
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } =
req.body;
const {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
@ -205,9 +207,13 @@ export const changePassword = async (req: Request, res: Response) => {
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv,
tag,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
@ -341,9 +347,12 @@ export const getBackupPrivateKey = async (req: Request, res: Response) => {
export const resetPassword = async (req: Request, res: Response) => {
try {
const {
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv,
tag,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
} = req.body;
@ -351,9 +360,13 @@ export const resetPassword = async (req: Request, res: Response) => {
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv,
tag,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},

@ -1,16 +1,12 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { NODE_ENV, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, INVITE_ONLY_SIGNUP } from '../../config';
import { User, MembershipOrg } from '../../models';
import { completeAccount } from '../../helpers/user';
import { User } from '../../models';
import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, INVITE_ONLY_SIGNUP } from '../../config';
import {
sendEmailVerification,
checkEmailVerification,
initializeDefaultOrg
} from '../../helpers/signup';
import { issueTokens, createToken } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables';
import axios from 'axios';
import { createToken } from '../../helpers/auth';
import { BadRequestError } from '../../utils/errors';
/**
@ -112,201 +108,3 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
token
});
};
/**
* Complete setting up user by adding their personal and auth information as part of the
* signup flow
* @param req
* @param res
* @returns
*/
export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier,
organizationName
} = req.body;
// get user
user = await User.findOne({ email });
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
});
}
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
});
if (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user
});
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
// issue tokens
const tokens = await issueTokens({
userId: user._id.toString()
});
token = tokens.token;
refreshToken = tokens.refreshToken;
// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
await axios.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + process.env.LOOPS_API_KEY
},
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
});
};
/**
* Complete setting up user by adding their personal and auth information as part of the
* invite flow
* @param req
* @param res
* @returns
*/
export const completeAccountInvite = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
} = req.body;
// get user
user = await User.findOne({ email });
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
});
}
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
});
if (!membershipOrg) throw new Error('Failed to find invitations for email');
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
publicKey,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
});
if (!user)
throw new Error('Failed to complete account for non-existent user');
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
// issue tokens
const tokens = await issueTokens({
userId: user._id.toString()
});
token = tokens.token;
refreshToken = tokens.refreshToken;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
user,
token,
refreshToken
});
};

@ -0,0 +1,351 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import * as Sentry from '@sentry/node';
import * as bigintConversion from 'bigint-conversion';
const jsrp = require('jsrp');
import { User, LoginSRPDetail } from '../../models';
import { issueAuthTokens, createToken } from '../../helpers/auth';
import { checkUserDevice } from '../../helpers/user';
import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services';
import { EELogService } from '../../ee/services';
import {
NODE_ENV,
JWT_MFA_LIFETIME,
JWT_MFA_SECRET
} from '../../config';
import { BadRequestError, InternalServerError } from '../../utils/errors';
import {
TOKEN_EMAIL_MFA,
ACTION_LOGIN
} from '../../variables';
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
}
}
const clientPublicKeys: any = {};
/**
* Log in user step 1: Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
* @param req
* @param res
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
clientPublicKey
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
if (!user) throw new Error('Failed to find user');
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false });
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to start authentication process'
});
}
};
/**
* Log in user step 2: complete step 2 of SRP protocol and return token and their (encrypted)
* private key
* @param req
* @param res
* @returns
*/
export const login2 = async (req: Request, res: Response) => {
try {
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user');
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP"))
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetail.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) {
// case: user has MFA enabled
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: JWT_MFA_LIFETIME,
secret: JWT_MFA_SECRET
});
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email
});
// send MFA code [code] to [email]
await sendMail({
template: 'emailMfa.handlebars',
subjectLine: 'Infisical MFA code',
recipients: [email],
substitutions: {
code
}
});
return res.status(200).send({
mfaEnabled: true,
token
});
}
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
// case: user does not have MFA enablgged
// return (access) token in response
interface ResponseData {
mfaEnabled: boolean;
encryptionVersion: any;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
}
const response: ResponseData = {
mfaEnabled: false,
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
}
if (
user?.protectedKey &&
user?.protectedKeyIV &&
user?.protectedKeyTag
) {
response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
});
return res.status(200).send(response);
}
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
};
/**
* Send MFA token to email [email]
* @param req
* @param res
*/
export const sendMfaToken = async (req: Request, res: Response) => {
try {
const { email } = req.body;
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email
});
// send MFA code [code] to [email]
await sendMail({
template: 'emailMfa.handlebars',
subjectLine: 'Infisical MFA code',
recipients: [email],
substitutions: {
code
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send MFA code'
});
}
return res.status(200).send({
message: 'Successfully sent new MFA code'
});
}
/**
* Verify MFA token [mfaToken] and issue JWT and refresh tokens if the
* MFA token [mfaToken] is valid
* @param req
* @param res
*/
export const verifyMfaToken = async (req: Request, res: Response) => {
const { email, mfaToken } = req.body;
await TokenService.validateToken({
type: TOKEN_EMAIL_MFA,
email,
token: mfaToken
});
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user');
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
// issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
const resObj: VerifyMfaTokenRes = {
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
});
return res.status(200).send(resObj);
}

@ -1,3 +1,5 @@
import * as authController from './authController';
import * as signupController from './signupController';
import * as usersController from './usersController';
import * as organizationsController from './organizationsController';
import * as workspaceController from './workspaceController';
@ -9,6 +11,8 @@ import * as environmentController from './environmentController';
import * as tagController from './tagController';
export {
authController,
signupController,
usersController,
organizationsController,
workspaceController,

@ -1,7 +1,8 @@
import to from 'await-to-js';
import { Types } from 'mongoose';
import { Request, Response } from 'express';
import { ISecret, Membership, Secret, Workspace } from '../../models';
import { ISecret, Secret } from '../../models';
import { IAction } from '../../ee/models';
import {
SECRET_PERSONAL,
SECRET_SHARED,
@ -20,6 +21,252 @@ import { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
import Tag from '../../models/tag';
import _ from 'lodash';
import {
BatchSecretRequest,
BatchSecret
} from '../../types/secret';
/**
* Peform a batch of any specified CUD secret operations
* @param req
* @param res
*/
export const batchSecrets = async (req: Request, res: Response) => {
const channel = getChannelFromUserAgent(req.headers['user-agent']);
const {
workspaceId,
environment,
requests
}: {
workspaceId: string;
environment: string;
requests: BatchSecretRequest[];
}= req.body;
const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = [];
const deleteSecrets: Types.ObjectId[] = [];
const actions: IAction[] = [];
requests.forEach((request) => {
switch (request.method) {
case 'POST':
createSecrets.push({
...request.secret,
version: 1,
user: request.secret.type === SECRET_PERSONAL ? req.user : undefined,
environment,
workspace: new Types.ObjectId(workspaceId)
});
break;
case 'PATCH':
updateSecrets.push({
...request.secret,
_id: new Types.ObjectId(request.secret._id)
});
break;
case 'DELETE':
deleteSecrets.push(new Types.ObjectId(request.secret._id));
break;
}
});
// handle create secrets
let createdSecrets: ISecret[] = [];
if (createSecrets.length > 0) {
createdSecrets = await Secret.insertMany(createSecrets);
// (EE) add secret versions for new secrets
await EESecretService.addSecretVersions({
secretVersions: createdSecrets.map((n: any) => {
return ({
...n._doc,
_id: new Types.ObjectId(),
secret: n._id,
isDeleted: false
});
})
});
const addAction = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: createdSecrets.map((n) => n._id)
}) as IAction;
actions.push(addAction);
if (postHogClient) {
postHogClient.capture({
event: 'secrets added',
distinctId: req.user.email,
properties: {
numberOfSecrets: createdSecrets.length,
environment,
workspaceId,
channel,
userAgent: req.headers?.['user-agent']
}
});
}
}
// handle update secrets
let updatedSecrets: ISecret[] = [];
if (updateSecrets.length > 0 && req.secrets) {
// construct object containing all secrets
let listedSecretsObj: {
[key: string]: {
version: number;
type: string;
}
} = {};
listedSecretsObj = req.secrets.reduce((obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
}), {});
const updateOperations = updateSecrets.map((u) => ({
updateOne: {
filter: { _id: new Types.ObjectId(u._id) },
update: {
$inc: {
version: 1
},
...u,
_id: new Types.ObjectId(u._id)
}
}
}));
await Secret.bulkWrite(updateOperations);
const secretVersions = updateSecrets.map((u) => ({
secret: new Types.ObjectId(u._id),
version: listedSecretsObj[u._id.toString()].version,
workspace: new Types.ObjectId(workspaceId),
type: listedSecretsObj[u._id.toString()].type,
environment,
isDeleted: false,
secretKeyCiphertext: u.secretKeyCiphertext,
secretKeyIV: u.secretKeyIV,
secretKeyTag: u.secretKeyTag,
secretValueCiphertext: u.secretValueCiphertext,
secretValueIV: u.secretValueIV,
secretValueTag: u.secretValueTag,
secretCommentCiphertext: u.secretCommentCiphertext,
secretCommentIV: u.secretCommentIV,
secretCommentTag: u.secretCommentTag,
tags: u.tags
}));
await EESecretService.addSecretVersions({
secretVersions
});
updatedSecrets = await Secret.find({
_id: {
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
}
});
const updateAction = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: updatedSecrets.map((u) => u._id)
}) as IAction;
actions.push(updateAction);
if (postHogClient) {
postHogClient.capture({
event: 'secrets modified',
distinctId: req.user.email,
properties: {
numberOfSecrets: updateSecrets.length,
environment,
workspaceId,
channel,
userAgent: req.headers?.['user-agent']
}
});
}
}
// handle delete secrets
if (deleteSecrets.length > 0) {
await Secret.deleteMany({
_id: {
$in: deleteSecrets
}
});
await EESecretService.markDeletedSecretVersions({
secretIds: deleteSecrets
});
const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: deleteSecrets
}) as IAction;
actions.push(deleteAction);
if (postHogClient) {
postHogClient.capture({
event: 'secrets deleted',
distinctId: req.user.email,
properties: {
numberOfSecrets: deleteSecrets.length,
environment,
workspaceId,
channel: channel,
userAgent: req.headers?.['user-agent']
}
});
}
}
if (actions.length > 0) {
// (EE) create (audit) log
await EELogService.createLog({
userId: req.user._id.toString(),
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress: req.ip
});
}
// // trigger event - push secrets
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId
})
});
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
});
const resObj: { [key: string]: ISecret[] | string[] } = {}
if (createSecrets.length > 0) {
resObj['createdSecrets'] = createdSecrets;
}
if (updateSecrets.length > 0) {
resObj['updatedSecrets'] = updatedSecrets;
}
if (deleteSecrets.length > 0) {
resObj['deletedSecrets'] = deleteSecrets.map((d) => d.toString());
}
return res.status(200).send(resObj);
}
/**
* Create secret(s) for workspace with id [workspaceId] and environment [environment]
@ -166,11 +413,9 @@ export const createSecrets = async (req: Request, res: Response) => {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
@ -187,11 +432,9 @@ export const createSecrets = async (req: Request, res: Response) => {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,

@ -0,0 +1,250 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { User, MembershipOrg } from '../../models';
import { completeAccount } from '../../helpers/user';
import {
initializeDefaultOrg
} from '../../helpers/signup';
import { issueAuthTokens } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables';
import { NODE_ENV } from '../../config';
import axios from 'axios';
/**
* Complete setting up user by adding their personal and auth information as part of the
* signup flow
* @param req
* @param res
* @returns
*/
export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName
}: {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
organizationName: string;
} = req.body;
// get user
user = await User.findOne({ email });
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
});
}
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
});
if (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user
});
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id.toString()
});
token = tokens.token;
// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
await axios.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + process.env.LOOPS_API_KEY
},
});
}
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
user,
token
});
};
/**
* Complete setting up user by adding their personal and auth information as part of the
* invite flow
* @param req
* @param res
* @returns
*/
export const completeAccountInvite = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
// get user
user = await User.findOne({ email });
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
});
}
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
});
if (!membershipOrg) throw new Error('Failed to find invitations for email');
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
});
if (!user)
throw new Error('Failed to complete account for non-existent user');
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id.toString()
});
token = tokens.token;
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
user,
token
});
};

@ -55,6 +55,44 @@ export const getMe = async (req: Request, res: Response) => {
});
}
/**
* Update the current user's MFA-enabled status [isMfaEnabled].
* Note: Infisical currently only supports email-based 2FA only; this will expand to
* include SMS and authenticator app modes of authentication in the future.
* @param req
* @param res
* @returns
*/
export const updateMyMfaEnabled = async (req: Request, res: Response) => {
let user;
try {
const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body;
req.user.isMfaEnabled = isMfaEnabled;
if (isMfaEnabled) {
// TODO: adapt this route/controller
// to work for different forms of MFA
req.user.mfaMethods = ['email'];
} else {
req.user.mfaMethods = [];
}
await req.user.save();
user = req.user;
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to update current user's MFA status"
});
}
return res.status(200).send({
user
});
}
/**
* Return organizations that the current user is part of.
* @param req

@ -158,11 +158,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
} = oldSecretVersion;
// update secret
@ -179,11 +177,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
},
{
new: true
@ -204,11 +200,9 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
secretValueTag
}).save();
// take secret snapshot

@ -5,22 +5,19 @@ import {
} from '../../variables';
export interface ISecretVersion {
_id: Types.ObjectId;
secret: Types.ObjectId;
version: number;
workspace: Types.ObjectId; // new
type: string; // new
user: Types.ObjectId; // new
user?: Types.ObjectId; // new
environment: string; // new
isDeleted: boolean;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretKeyHash: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretValueHash: string;
tags?: string[];
}
@ -72,9 +69,6 @@ const secretVersionSchema = new Schema<ISecretVersion>(
type: String, // symmetric
required: true
},
secretKeyHash: {
type: String
},
secretValueCiphertext: {
type: String,
required: true
@ -87,9 +81,6 @@ const secretVersionSchema = new Schema<ISecretVersion>(
type: String, // symmetric
required: true
},
secretValueHash: {
type: String
},
tags: {
ref: 'Tag',
type: [Schema.Types.ObjectId],

@ -211,7 +211,7 @@ const getAuthAPIKeyPayload = async ({
* @return {String} obj.token - issued JWT token
* @return {String} obj.refreshToken - issued refresh token
*/
const issueTokens = async ({ userId }: { userId: string }) => {
const issueAuthTokens = async ({ userId }: { userId: string }) => {
let token: string;
let refreshToken: string;
try {
@ -298,6 +298,6 @@ export {
getAuthSTDPayload,
getAuthAPIKeyPayload,
createToken,
issueTokens,
issueAuthTokens,
clearTokens
};

@ -1,13 +1,11 @@
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
import { Token, IToken, IUser } from '../models';
import { IUser } from '../models';
import { createOrganization } from './organization';
import { addMembershipsOrg } from './membershipOrg';
import { createWorkspace } from './workspace';
import { addMemberships } from './membership';
import { OWNER, ADMIN, ACCEPTED } from '../variables';
import { OWNER, ACCEPTED } from '../variables';
import { sendMail } from '../helpers/nodemailer';
import { EMAIL_TOKEN_LIFETIME } from '../config';
import { TokenService } from '../services';
import { TOKEN_EMAIL_CONFIRMATION } from '../variables';
/**
* Send magic link to verify email to [email]
@ -15,22 +13,13 @@ import { EMAIL_TOKEN_LIFETIME } from '../config';
* @param {Object} obj
* @param {String} obj.email - email
* @returns {Boolean} success - whether or not operation was successful
*
*/
const sendEmailVerification = async ({ email }: { email: string }) => {
try {
const token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
await Token.findOneAndUpdate(
{ email },
{
email,
token,
createdAt: new Date(),
ttl: Math.floor(+new Date() / 1000) + EMAIL_TOKEN_LIFETIME // time in seconds, i.e unix
},
{ upsert: true, new: true }
);
const token = await TokenService.createToken({
type: TOKEN_EMAIL_CONFIRMATION,
email
});
// send mail
await sendMail({
@ -64,21 +53,11 @@ const checkEmailVerification = async ({
code: string;
}) => {
try {
const token = await Token.findOne({
await TokenService.validateToken({
type: TOKEN_EMAIL_CONFIRMATION,
email,
token: code
});
if (token && Math.floor(Date.now() / 1000) > token.ttl) {
await Token.deleteOne({
email,
token: code
});
throw new Error('Verification token has expired')
}
if (!token) throw new Error('Failed to find email verification token');
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@ -114,18 +93,6 @@ const initializeDefaultOrg = async ({
roles: [OWNER],
statuses: [ACCEPTED]
});
// initialize a default workspace inside the new organization
const workspace = await createWorkspace({
name: `Example Project`,
organizationId: organization._id.toString()
});
await addMemberships({
userIds: [user._id.toString()],
workspaceId: workspace._id.toString(),
roles: [ADMIN]
});
} catch (err) {
throw new Error(`Failed to initialize default organization and workspace [err=${err}]`);
}

@ -0,0 +1,217 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { TokenData } from '../models';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET
} from '../variables';
import {
SALT_ROUNDS
} from '../config';
import { UnauthorizedRequestError } from '../utils/errors';
/**
* Create and store a token in the database for purpose [type]
* @param {Object} obj
* @param {String} obj.type
* @param {String} obj.email
* @param {String} obj.phoneNumber
* @param {Types.ObjectId} obj.organizationId
* @returns {String} token - the created token
*/
const createTokenHelper = async ({
type,
email,
phoneNumber,
organizationId
}: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset';
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId
}) => {
let token, expiresAt, triesLeft;
try {
// generate random token based on specified token use-case
// type [type]
switch (type) {
case TOKEN_EMAIL_CONFIRMATION:
// generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
expiresAt = new Date((new Date()).getTime() + 86400000);
break;
case TOKEN_EMAIL_MFA:
// generate random 6-digit code
token = String(crypto.randomInt(Math.pow(10, 5), Math.pow(10, 6) - 1));
triesLeft = 5;
expiresAt = new Date((new Date()).getTime() + 300000);
break;
case TOKEN_EMAIL_ORG_INVITATION:
// generate random hex
token = crypto.randomBytes(16).toString('hex');
expiresAt = new Date((new Date()).getTime() + 259200000);
break;
case TOKEN_EMAIL_PASSWORD_RESET:
// generate random hex
token = crypto.randomBytes(16).toString('hex');
expiresAt = new Date((new Date()).getTime() + 86400000);
break;
default:
token = crypto.randomBytes(16).toString('hex');
expiresAt = new Date();
break;
}
interface TokenDataQuery {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
interface TokenDataUpdate {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
triesLeft?: number;
expiresAt: Date;
}
const query: TokenDataQuery = { type };
const update: TokenDataUpdate = {
type,
tokenHash: await bcrypt.hash(token, SALT_ROUNDS),
expiresAt
}
if (email) {
query.email = email;
update.email = email;
}
if (phoneNumber) {
query.phoneNumber = phoneNumber;
update.phoneNumber = phoneNumber;
}
if (organizationId) {
query.organization = organizationId
update.organization = organizationId
}
if (triesLeft) {
update.triesLeft = triesLeft;
}
await TokenData.findOneAndUpdate(
query,
update,
{
new: true,
upsert: true
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(
"Failed to create token"
);
}
return token;
}
/**
*
* @param {Object} obj
* @param {String} obj.email - email associated with the token
* @param {String} obj.token - value of the token
*/
const validateTokenHelper = async ({
type,
email,
phoneNumber,
organizationId,
token
}: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset';
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId;
token: string;
}) => {
interface Query {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
}
const query: Query = { type };
if (email) { query.email = email; }
if (phoneNumber) { query.phoneNumber = phoneNumber; }
if (organizationId) { query.organization = organizationId; }
const tokenData = await TokenData.findOne(query).select('+tokenHash');
if (!tokenData) throw new Error('Failed to find token to validate');
if (tokenData.expiresAt < new Date()) {
// case: token expired
await TokenData.findByIdAndDelete(tokenData._id);
throw UnauthorizedRequestError({
message: 'MFA session expired. Please log in again',
context: {
code: 'mfa_expired'
}
});
}
const isValid = await bcrypt.compare(token, tokenData.tokenHash);
if (!isValid) {
// case: token is not valid
if (tokenData?.triesLeft !== undefined) {
// case: token has a try-limit
if (tokenData.triesLeft === 1) {
// case: token is out of tries
await TokenData.findByIdAndDelete(tokenData._id);
} else {
// case: token has more than 1 try left
await TokenData.findByIdAndUpdate(tokenData._id, {
triesLeft: tokenData.triesLeft - 1
}, {
new: true
});
}
throw UnauthorizedRequestError({
message: 'MFA code is invalid',
context: {
code: 'mfa_invalid',
triesLeft: tokenData.triesLeft - 1
}
});
}
throw UnauthorizedRequestError({
message: 'MFA code is invalid',
context: {
code: 'mfa_invalid'
}
});
}
// case: token is valid
await TokenData.findByIdAndDelete(tokenData._id);
}
export {
createTokenHelper,
validateTokenHelper
}

@ -1,5 +1,6 @@
import * as Sentry from '@sentry/node';
import { User, IUser } from '../models';
import { IUser, User } from '../models';
import { sendMail } from './nodemailer';
/**
* Initialize a user under email [email]
@ -28,10 +29,14 @@ const setupAccount = async ({ email }: { email: string }) => {
* @param {String} obj.userId - id of user to finish setting up
* @param {String} obj.firstName - first name of user
* @param {String} obj.lastName - last name of user
* @param {Number} obj.encryptionVersion - version of auth encryption scheme used
* @param {String} obj.protectedKey - protected key in encryption version 2
* @param {String} obj.protectedKeyIV - IV of protected key in encryption version 2
* @param {String} obj.protectedKeyTag - tag of protected key in encryption version 2
* @param {String} obj.publicKey - publickey of user
* @param {String} obj.encryptedPrivateKey - (encrypted) private key of user
* @param {String} obj.iv - iv for (encrypted) private key of user
* @param {String} obj.tag - tag for (encrypted) private key of user
* @param {String} obj.encryptedPrivateKeyIV - iv for (encrypted) private key of user
* @param {String} obj.encryptedPrivateKeyTag - tag for (encrypted) private key of user
* @param {String} obj.salt - salt for auth SRP
* @param {String} obj.verifier - verifier for auth SRP
* @returns {Object} user - the completed user
@ -40,20 +45,28 @@ const completeAccount = async ({
userId,
firstName,
lastName,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv,
tag,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
}: {
userId: string;
firstName: string;
lastName: string;
encryptionVersion: number;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
}) => {
@ -67,10 +80,14 @@ const completeAccount = async ({
{
firstName,
lastName,
encryptionVersion,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv,
tag,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
@ -85,4 +102,48 @@ const completeAccount = async ({
return user;
};
export { setupAccount, completeAccount };
/**
* Check if device with ip [ip] and user-agent [userAgent] has been seen for user [user].
* If the device is unseen, then notify the user of the new device
* @param {Object} obj
* @param {String} obj.ip - login ip address
* @param {String} obj.userAgent - login user-agent
*/
const checkUserDevice = async ({
user,
ip,
userAgent
}: {
user: IUser;
ip: string;
userAgent: string;
}) => {
const isDeviceSeen = user.devices.some((device) => device.ip === ip && device.userAgent === userAgent);
if (!isDeviceSeen) {
// case: unseen login ip detected for user
// -> notify user about the sign-in from new ip
user.devices = user.devices.concat([{
ip: String(ip),
userAgent
}]);
await user.save();
// send MFA code [code] to [email]
await sendMail({
template: 'newDevice.handlebars',
subjectLine: `Successful login from new device`,
recipients: [user.email],
substitutions: {
email: user.email,
timestamp: new Date().toString(),
ip,
userAgent
}
});
}
}
export { setupAccount, completeAccount, checkUserDevice };

@ -138,7 +138,7 @@ const exchangeCodeAzure = async ({
try {
res = (await axios.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
scope: 'https://vault.azure.net/.default openid offline_access',
@ -147,16 +147,16 @@ const exchangeCodeAzure = async ({
redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback`
} as any)
)).data;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err: any) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Azure');
}
return ({
accessToken: res.access_token,
refreshToken: res.refresh_token,
@ -175,36 +175,36 @@ const exchangeCodeAzure = async ({
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeHeroku = async ({
code
code
}: {
code: string;
code: string;
}) => {
let res: ExchangeCodeHerokuResponse;
const accessExpiresAt = new Date();
try {
res = (await axios.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_secret: CLIENT_SECRET_HEROKU
} as any)
)).data;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Heroku');
}
return ({
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt
});
let res: ExchangeCodeHerokuResponse;
const accessExpiresAt = new Date();
try {
res = (await axios.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_secret: CLIENT_SECRET_HEROKU
} as any)
)).data;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Heroku');
}
return ({
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt
});
}
/**
@ -234,7 +234,7 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Vercel');
throw new Error(`Failed OAuth2 code-token exchange with Vercel [err=${err}]`);
}
return {

@ -30,7 +30,6 @@ import {
INTEGRATION_FLYIO_API_URL,
INTEGRATION_CIRCLECI_API_URL,
} from "../variables";
import { access, appendFile } from "fs";
/**
* Sync/push [secrets] to [app] in integration named [integration]
@ -181,7 +180,8 @@ const syncSecretsAzureKeyVault = async ({
while (url) {
const res = await axios.get(url, {
headers: {
Authorization: `Bearer ${accessToken}`
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
});
@ -202,7 +202,8 @@ const syncSecretsAzureKeyVault = async ({
const azureKeyVaultSecret = await axios.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
headers: {
'Authorization': `Bearer ${accessToken}`
'Authorization': `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
});
@ -259,7 +260,8 @@ const syncSecretsAzureKeyVault = async ({
},
{
headers: {
Authorization: `Bearer ${accessToken}`
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
}
);
@ -270,7 +272,8 @@ const syncSecretsAzureKeyVault = async ({
deleteSecrets.forEach(async (secret) => {
await axios.delete(`${integration.app}/secrets/${secret.key}?api-version=7.3`, {
headers: {
'Authorization': `Bearer ${accessToken}`
'Authorization': `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
});
});
@ -488,6 +491,7 @@ const syncSecretsHeroku = async ({
headers: {
Accept: "application/vnd.heroku+json; version=3",
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
)
@ -506,6 +510,7 @@ const syncSecretsHeroku = async ({
headers: {
Accept: "application/vnd.heroku+json; version=3",
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -552,7 +557,7 @@ const syncSecretsVercel = async ({
}
: {}),
};
const res = (
await Promise.all(
(
@ -561,7 +566,8 @@ const syncSecretsVercel = async ({
{
params,
headers: {
Authorization: `Bearer ${accessToken}`
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
}
))
@ -573,7 +579,8 @@ const syncSecretsVercel = async ({
{
params,
headers: {
Authorization: `Bearer ${accessToken}`
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
}
)).data)
@ -633,6 +640,7 @@ const syncSecretsVercel = async ({
params,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -649,6 +657,7 @@ const syncSecretsVercel = async ({
params,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -664,6 +673,7 @@ const syncSecretsVercel = async ({
params,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -723,6 +733,7 @@ const syncSecretsNetlify = async ({
params: getParams,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
)
@ -837,6 +848,7 @@ const syncSecretsNetlify = async ({
params: syncParams,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -854,6 +866,7 @@ const syncSecretsNetlify = async ({
params: syncParams,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -868,6 +881,7 @@ const syncSecretsNetlify = async ({
params: syncParams,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -882,6 +896,7 @@ const syncSecretsNetlify = async ({
params: syncParams,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -1035,6 +1050,7 @@ const syncSecretsRender = async ({
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
},
}
);
@ -1088,6 +1104,7 @@ const syncSecretsFlyio = async ({
method: "post",
headers: {
Authorization: "Bearer " + accessToken,
'Accept-Encoding': 'application/json'
},
data: {
query: SetSecrets,
@ -1167,6 +1184,7 @@ const syncSecretsFlyio = async ({
headers: {
Authorization: "Bearer " + accessToken,
"Content-Type": "application/json",
'Accept-Encoding': 'application/json'
},
data: {
query: DeleteSecrets,

@ -1,4 +1,5 @@
import requireAuth from './requireAuth';
import requireMfaAuth from './requireMfaAuth';
import requireBotAuth from './requireBotAuth';
import requireSignupAuth from './requireSignupAuth';
import requireWorkspaceAuth from './requireWorkspaceAuth';
@ -15,6 +16,7 @@ import validateRequest from './validateRequest';
export {
requireAuth,
requireMfaAuth,
requireBotAuth,
requireSignupAuth,
requireWorkspaceAuth,

@ -1,11 +1,12 @@
import { ErrorRequestHandler } from "express";
import * as Sentry from '@sentry/node';
import { InternalServerError } from "../utils/errors";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError";
import { NODE_ENV } from "../config";
import { TokenExpiredError } from 'jsonwebtoken';
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
if (res.headersSent) return next();

@ -0,0 +1,43 @@
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { User } from '../models';
import { JWT_MFA_SECRET } from '../config';
import { BadRequestError, UnauthorizedRequestError } from '../utils/errors';
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
}
}
/**
* Validate if (MFA) JWT temporary token on request is valid (e.g. not expired)
* and if there is an associated user.
*/
const requireMfaAuth = async (
req: Request,
res: Response,
next: NextFunction
) => {
// JWT (temporary) authentication middleware for complete signup
const [ AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE ] = <[string, string]>req.headers['authorization']?.split(' ', 2) ?? [null, null]
if(AUTH_TOKEN_TYPE === null) return next(BadRequestError({message: `Missing Authorization Header in the request header.`}))
if(AUTH_TOKEN_TYPE.toLowerCase() !== 'bearer') return next(BadRequestError({message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`}))
if(AUTH_TOKEN_VALUE === null) return next(BadRequestError({message: 'Missing Authorization Body in the request header'}))
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, JWT_MFA_SECRET)
);
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
if (!user)
return next(UnauthorizedRequestError({message: 'Unable to authenticate for User account completion. Try logging in again'}))
req.user = user;
return next();
};
export default requireMfaAuth;

@ -10,7 +10,7 @@ import MembershipOrg, { IMembershipOrg } from './membershipOrg';
import Organization, { IOrganization } from './organization';
import Secret, { ISecret } from './secret';
import ServiceToken, { IServiceToken } from './serviceToken';
import Token, { IToken } from './token';
import TokenData, { ITokenData } from './tokenData';
import User, { IUser } from './user';
import UserAction, { IUserAction } from './userAction';
import Workspace, { IWorkspace } from './workspace';
@ -43,8 +43,8 @@ export {
ISecret,
ServiceToken,
IServiceToken,
Token,
IToken,
TokenData,
ITokenData,
User,
IUser,
UserAction,

@ -5,7 +5,7 @@ export interface IToken {
email: string;
token: string;
createdAt: Date;
ttl: Number;
ttl: number;
}
const tokenSchema = new Schema<IToken>({

@ -0,0 +1,61 @@
import { Schema, Types, model } from 'mongoose';
export interface ITokenData {
type: string;
email?: string;
phoneNumber?: string;
organization?: Types.ObjectId;
tokenHash: string;
triesLeft?: number;
expiresAt: Date;
createdAt: Date;
updatedAt: Date;
}
const tokenDataSchema = new Schema<ITokenData>({
type: {
type: String,
enum: [
'emailConfirmation',
'emailMfa',
'organizationInvitation',
'passwordReset'
],
required: true
},
email: {
type: String
},
phoneNumber: {
type: String
},
organization: { // organizationInvitation-specific field
type: Schema.Types.ObjectId,
ref: 'Organization'
},
tokenHash: {
type: String,
select: false,
required: true
},
triesLeft: {
type: Number
},
expiresAt: {
type: Date,
expires: 0,
required: true
}
}, {
timestamps: true
});
tokenDataSchema.index({
expiresAt: 1
}, {
expireAfterSeconds: 0
});
const TokenData = model<ITokenData>('TokenData', tokenDataSchema);
export default TokenData;

@ -1,10 +1,14 @@
import { Schema, model, Types } from 'mongoose';
import { Schema, model, Types, Document } from 'mongoose';
export interface IUser {
export interface IUser extends Document {
_id: Types.ObjectId;
email: string;
firstName?: string;
lastName?: string;
encryptionVersion: number;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
@ -12,7 +16,12 @@ export interface IUser {
salt?: string;
verifier?: string;
refreshVersion?: number;
seenIps: [string];
isMfaEnabled: boolean;
mfaMethods: boolean;
devices: {
ip: string;
userAgent: string;
}[];
}
const userSchema = new Schema<IUser>(
@ -27,6 +36,23 @@ const userSchema = new Schema<IUser>(
lastName: {
type: String
},
encryptionVersion: {
type: Number,
select: false,
default: 1 // to resolve backward-compatibility issues
},
protectedKey: { // introduced as part of encryption version 2
type: String,
select: false
},
protectedKeyIV: { // introduced as part of encryption version 2
type: String,
select: false
},
protectedKeyTag: { // introduced as part of encryption version 2
type: String,
select: false
},
publicKey: {
type: String,
select: false
@ -35,11 +61,11 @@ const userSchema = new Schema<IUser>(
type: String,
select: false
},
iv: {
iv: { // iv of [encryptedPrivateKey]
type: String,
select: false
},
tag: {
tag: { // tag of [encryptedPrivateKey]
type: String,
select: false
},
@ -56,8 +82,21 @@ const userSchema = new Schema<IUser>(
default: 0,
select: false
},
seenIps: [String]
},
isMfaEnabled: {
type: Boolean,
default: false
},
mfaMethods: [{
type: String
}],
devices: {
type: [{
ip: String,
userAgent: String
}],
default: []
}
},
{
timestamps: true
}

@ -7,7 +7,7 @@ import { authLimiter } from '../../helpers/rateLimiter';
router.post('/token', validateRequest, authController.getNewToken);
router.post(
router.post( // deprecated (moved to api/v2/auth/login1)
'/login1',
authLimiter,
body('email').exists().trim().notEmpty(),
@ -16,7 +16,7 @@ router.post(
authController.login1
);
router.post(
router.post( // deprecated (moved to api/v2/auth/login2)
'/login2',
authLimiter,
body('email').exists().trim().notEmpty(),

@ -31,7 +31,7 @@ router.patch(
requireBotAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
body('isActive').isBoolean(),
body('isActive').exists().isBoolean(),
body('botKey'),
validateRequest,
botController.setBotActiveState

@ -10,7 +10,7 @@ router.post(
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('clientPublicKey').exists().trim().notEmpty(),
body('clientPublicKey').exists().isString().trim().notEmpty(),
validateRequest,
passwordController.srp1
);
@ -22,11 +22,14 @@ router.post(
acceptedAuthModes: ['jwt']
}),
body('clientProof').exists().trim().notEmpty(),
body('encryptedPrivateKey').exists().trim().notEmpty().notEmpty(), // private key encrypted under new pwd
body('iv').exists().trim().notEmpty(), // new iv for private key
body('tag').exists().trim().notEmpty(), // new tag for private key
body('salt').exists().trim().notEmpty(), // part of new pwd
body('verifier').exists().trim().notEmpty(), // part of new pwd
body('protectedKey').exists().isString().trim().notEmpty(),
body('protectedKeyIV').exists().isString().trim().notEmpty(),
body('protectedKeyTag').exists().isString().trim().notEmpty(),
body('encryptedPrivateKey').exists().isString().trim().notEmpty(), // private key encrypted under new pwd
body('encryptedPrivateKeyIV').exists().isString().trim().notEmpty(), // new iv for private key
body('encryptedPrivateKeyTag').exists().isString().trim().notEmpty(), // new tag for private key
body('salt').exists().isString().trim().notEmpty(), // part of new pwd
body('verifier').exists().isString().trim().notEmpty(), // part of new pwd
validateRequest,
passwordController.changePassword
);
@ -34,7 +37,7 @@ router.post(
router.post(
'/email/password-reset',
passwordLimiter,
body('email').exists().trim().notEmpty(),
body('email').exists().isString().trim().notEmpty().isEmail(),
validateRequest,
passwordController.emailPasswordReset
);
@ -42,8 +45,8 @@ router.post(
router.post(
'/email/password-reset-verify',
passwordLimiter,
body('email').exists().trim().notEmpty().isEmail(),
body('code').exists().trim().notEmpty(),
body('email').exists().isString().trim().notEmpty().isEmail(),
body('code').exists().isString().trim().notEmpty(),
validateRequest,
passwordController.emailPasswordResetVerify
);
@ -61,12 +64,12 @@ router.post(
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('clientProof').exists().trim().notEmpty(),
body('encryptedPrivateKey').exists().trim().notEmpty(), // (backup) private key encrypted under a strong key
body('iv').exists().trim().notEmpty(), // new iv for (backup) private key
body('tag').exists().trim().notEmpty(), // new tag for (backup) private key
body('salt').exists().trim().notEmpty(), // salt generated from strong key
body('verifier').exists().trim().notEmpty(), // salt generated from strong key
body('clientProof').exists().isString().trim().notEmpty(),
body('encryptedPrivateKey').exists().isString().trim().notEmpty(), // (backup) private key encrypted under a strong key
body('iv').exists().isString().trim().notEmpty(), // new iv for (backup) private key
body('tag').exists().isString().trim().notEmpty(), // new tag for (backup) private key
body('salt').exists().isString().trim().notEmpty(), // salt generated from strong key
body('verifier').exists().isString().trim().notEmpty(), // salt generated from strong key
validateRequest,
passwordController.createBackupPrivateKey
);
@ -74,11 +77,14 @@ router.post(
router.post(
'/password-reset',
requireSignupAuth,
body('encryptedPrivateKey').exists().trim().notEmpty(), // private key encrypted under new pwd
body('iv').exists().trim().notEmpty(), // new iv for private key
body('tag').exists().trim().notEmpty(), // new tag for private key
body('salt').exists().trim().notEmpty(), // part of new pwd
body('verifier').exists().trim().notEmpty(), // part of new pwd
body('protectedKey').exists().isString().trim().notEmpty(),
body('protectedKeyIV').exists().isString().trim().notEmpty(),
body('protectedKeyTag').exists().isString().trim().notEmpty(),
body('encryptedPrivateKey').exists().isString().trim().notEmpty(), // private key encrypted under new pwd
body('encryptedPrivateKeyIV').exists().isString().trim().notEmpty(), // new iv for private key
body('encryptedPrivateKeyTag').exists().isString().trim().notEmpty(), // new tag for private key
body('salt').exists().isString().trim().notEmpty(), // part of new pwd
body('verifier').exists().isString().trim().notEmpty(), // part of new pwd
validateRequest,
passwordController.resetPassword
);

@ -1,7 +1,7 @@
import express from 'express';
const router = express.Router();
import { body } from 'express-validator';
import { requireSignupAuth, validateRequest } from '../../middleware';
import { validateRequest } from '../../middleware';
import { signupController } from '../../controllers/v1';
import { authLimiter } from '../../helpers/rateLimiter';
@ -22,39 +22,4 @@ router.post(
signupController.verifyEmailSignup
);
router.post(
'/complete-account/signup',
authLimiter,
requireSignupAuth,
body('email').exists().trim().notEmpty().isEmail(),
body('firstName').exists().trim().notEmpty(),
body('lastName').exists().trim().notEmpty(),
body('publicKey').exists().trim().notEmpty(),
body('encryptedPrivateKey').exists().trim().notEmpty(),
body('iv').exists().trim().notEmpty(),
body('tag').exists().trim().notEmpty(),
body('salt').exists().trim().notEmpty(),
body('verifier').exists().trim().notEmpty(),
body('organizationName').exists().trim().notEmpty(),
validateRequest,
signupController.completeAccountSignup
);
router.post(
'/complete-account/invite',
authLimiter,
requireSignupAuth,
body('email').exists().trim().notEmpty().isEmail(),
body('firstName').exists().trim().notEmpty(),
body('lastName').exists().trim().notEmpty(),
body('publicKey').exists().trim().notEmpty(),
body('encryptedPrivateKey').exists().trim().notEmpty(),
body('iv').exists().trim().notEmpty(),
body('tag').exists().trim().notEmpty(),
body('salt').exists().trim().notEmpty(),
body('verifier').exists().trim().notEmpty(),
validateRequest,
signupController.completeAccountInvite
);
export default router;

@ -0,0 +1,44 @@
import express from 'express';
const router = express.Router();
import { body } from 'express-validator';
import { requireMfaAuth, validateRequest } from '../../middleware';
import { authController } from '../../controllers/v2';
import { authLimiter } from '../../helpers/rateLimiter';
router.post(
'/login1',
authLimiter,
body('email').isString().trim().notEmpty(),
body('clientPublicKey').isString().trim().notEmpty(),
validateRequest,
authController.login1
);
router.post(
'/login2',
authLimiter,
body('email').isString().trim().notEmpty(),
body('clientProof').isString().trim().notEmpty(),
validateRequest,
authController.login2
);
router.post(
'/mfa/send',
authLimiter,
body('email').isString().trim().notEmpty(),
validateRequest,
authController.sendMfaToken
);
router.post(
'/mfa/verify',
authLimiter,
requireMfaAuth,
body('email').isString().trim().notEmpty(),
body('mfaToken').isString().trim().notEmpty(),
validateRequest,
authController.verifyMfaToken
);
export default router;

@ -1,3 +1,5 @@
import auth from './auth';
import signup from './signup';
import users from './users';
import organizations from './organizations';
import workspace from './workspace';
@ -9,6 +11,8 @@ import environment from "./environment"
import tags from "./tags"
export {
auth,
signup,
users,
organizations,
workspace,

@ -6,14 +6,52 @@ import {
requireSecretsAuth,
validateRequest
} from '../../middleware';
import { query, check, body } from 'express-validator';
import { query, body } from 'express-validator';
import { secretsController } from '../../controllers/v2';
import { validateSecrets } from '../../helpers/secret';
import {
ADMIN,
MEMBER,
SECRET_PERSONAL,
SECRET_SHARED
} from '../../variables';
import {
BatchSecretRequest
} from '../../types/secret';
router.post(
'/batch',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
location: 'body'
}),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('requests')
.exists()
.custom(async (requests: BatchSecretRequest[], { req }) => {
if (Array.isArray(requests)) {
const secretIds = requests
.map((request) => request.secret._id)
.filter((secretId) => secretId !== undefined)
if (secretIds.length > 0) {
const relevantSecrets = await validateSecrets({
userId: req.user._id.toString(),
secretIds
});
req.secrets = relevantSecrets;
}
}
return true;
}),
validateRequest,
secretsController.batchSecrets
);
router.post(
'/',

@ -0,0 +1,49 @@
import express from 'express';
const router = express.Router();
import { body } from 'express-validator';
import { requireSignupAuth, validateRequest } from '../../middleware';
import { signupController } from '../../controllers/v2';
import { authLimiter } from '../../helpers/rateLimiter';
router.post(
'/complete-account/signup',
authLimiter,
requireSignupAuth,
body('email').exists().isString().trim().notEmpty().isEmail(),
body('firstName').exists().isString().trim().notEmpty(),
body('lastName').exists().isString().trim().notEmpty(),
body('protectedKey').exists().isString().trim().notEmpty(),
body('protectedKeyIV').exists().isString().trim().notEmpty(),
body('protectedKeyTag').exists().isString().trim().notEmpty(),
body('publicKey').exists().isString().trim().notEmpty(),
body('encryptedPrivateKey').exists().isString().trim().notEmpty(),
body('encryptedPrivateKeyIV').exists().isString().trim().notEmpty(),
body('encryptedPrivateKeyTag').exists().isString().trim().notEmpty(),
body('salt').exists().isString().trim().notEmpty(),
body('verifier').exists().isString().trim().notEmpty(),
body('organizationName').exists().isString().trim().notEmpty(),
validateRequest,
signupController.completeAccountSignup
);
router.post(
'/complete-account/invite',
authLimiter,
requireSignupAuth,
body('email').exists().isString().trim().notEmpty().isEmail(),
body('firstName').exists().isString().trim().notEmpty(),
body('lastName').exists().isString().trim().notEmpty(),
body('protectedKey').exists().isString().trim().notEmpty(),
body('protectedKeyIV').exists().isString().trim().notEmpty(),
body('protectedKeyTag').exists().isString().trim().notEmpty(),
body('publicKey').exists().trim().notEmpty(),
body('encryptedPrivateKey').exists().isString().trim().notEmpty(),
body('encryptedPrivateKeyIV').exists().isString().trim().notEmpty(),
body('encryptedPrivateKeyTag').exists().isString().trim().notEmpty(),
body('salt').exists().isString().trim().notEmpty(),
body('verifier').exists().isString().trim().notEmpty(),
validateRequest,
signupController.completeAccountInvite
);
export default router;

@ -1,8 +1,10 @@
import express from 'express';
const router = express.Router();
import {
requireAuth
requireAuth,
validateRequest
} from '../../middleware';
import { body } from 'express-validator';
import { usersController } from '../../controllers/v2';
router.get(
@ -13,6 +15,16 @@ router.get(
usersController.getMe
);
router.patch(
'/me/mfa',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
body('isMfaEnabled').exists().isBoolean(),
validateRequest,
usersController.updateMyMfaEnabled
);
router.get(
'/me/organizations',
requireAuth({

@ -0,0 +1,69 @@
import { Types } from 'mongoose';
import { createTokenHelper, validateTokenHelper } from '../helpers/token';
/**
* Class to handle token actions
* TODO: elaborate more on this class
*/
class TokenService {
/**
* Create a token [token] for type [type] with associated details
* @param {Object} obj
* @param {String} obj.type - type or context of token (e.g. emailConfirmation)
* @param {String} obj.email - email associated with the token
* @param {String} obj.phoneNumber - phone number associated with the token
* @param {Types.ObjectId} obj.organizationId - id of organization associated with the token
* @returns {String} token - the token to create
*/
static async createToken({
type,
email,
phoneNumber,
organizationId
}: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset';
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId;
}) {
return await createTokenHelper({
type,
email,
phoneNumber,
organizationId
});
}
/**
* Validate whether or not token [token] and its associated details match a token in the DB
* @param {Object} obj
* @param {String} obj.type - type or context of token (e.g. emailConfirmation)
* @param {String} obj.email - email associated with the token
* @param {String} obj.phoneNumber - phone number associated with the token
* @param {Types.ObjectId} obj.organizationId - id of organization associated with the token
* @param {String} obj.token - the token to validate
*/
static async validateToken({
type,
email,
phoneNumber,
organizationId,
token
}: {
type: 'emailConfirmation' | 'emailMfa' | 'organizationInvitation' | 'passwordReset';
email?: string;
phoneNumber?: string;
organizationId?: Types.ObjectId;
token: string;
}) {
return await validateTokenHelper({
type,
email,
phoneNumber,
organizationId,
token
});
}
}
export default TokenService;

@ -3,11 +3,13 @@ import postHogClient from './PostHogClient';
import BotService from './BotService';
import EventService from './EventService';
import IntegrationService from './IntegrationService';
import TokenService from './TokenService';
export {
DatabaseService,
postHogClient,
BotService,
EventService,
IntegrationService
IntegrationService,
TokenService
}

@ -1,6 +1,10 @@
import nodemailer from 'nodemailer';
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config';
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from '../variables';
import {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS
} from '../variables';
import SMTPConnection from 'nodemailer/lib/smtp-connection';
import * as Sentry from '@sentry/node';
@ -27,6 +31,12 @@ if (SMTP_SECURE) {
ciphers: 'TLSv1.2'
}
break;
case SMTP_HOST_SOCKETLABS:
mailOpts.requireTLS = true;
mailOpts.tls = {
ciphers: 'TLSv1.2'
}
break;
default:
if (SMTP_HOST.includes('amazonaws.com')) {
mailOpts.tls = {

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>MFA Code</title>
</head>
<body>
<h2>Infisical</h2>
<h2>Sign in attempt requires further verification</h2>
<p>Your MFA code is below — enter it where you started signing in to Infisical.</p>
<h2>{{code}}</h2>
<p>The MFA code will be valid for 2 minutes.</p>
<p>Not you? Contact Infisical or your administrator immediately.</p>
</body>
</html>

@ -1,14 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Email Verification</title>
<title>Code</title>
</head>
<body>
<h2>Confirm your email address</h2>
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
<h1>{{code}}</h1>
<p>Questions about setting up Infisical? Email us at support@infisical.com</p>
</body>
</html>

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Successful login for {{email}} from new device</title>
</head>
<body>
<h2>Infisical</h2>
<p>We're verifying a recent login for {{email}}:</p>
<p><strong>Timestamp</strong>: {{timestamp}}</p>
<p><strong>IP address</strong>: {{ip}}</p>
<p><strong>User agent</strong>: {{userAgent}}</p>
<p>If you believe that this login is suspicious, please contact Infisical or reset your password immediately.</p>
</body>
</html>

@ -1,5 +1,7 @@
import { Types } from 'mongoose';
import { Assign, Omit } from 'utility-types';
import { ISecret } from '../../models';
import { mongo } from 'mongoose';
// Everything is required, except the omitted types
export type CreateSecretRequestBody = Omit<ISecret, "user" | "version" | "environment" | "workspace">;
@ -12,3 +14,39 @@ export type SanitizedSecretModify = Partial<Omit<ISecret, "user" | "version" | "
// Everything is required, except the omitted types
export type SanitizedSecretForCreate = Omit<ISecret, "version" | "_id">;
export interface BatchSecretRequest {
id: string;
method: 'POST' | 'PATCH' | 'DELETE';
secret: Secret;
}
export interface BatchSecret {
_id: string;
type: 'shared' | 'personal',
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
tags: string[];
}
export interface BatchSecret {
_id: string;
type: 'shared' | 'personal',
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretCommentCiphertext: string;
secretCommentIV: string;
secretCommentTag: string;
tags: string[];
}

@ -40,10 +40,23 @@ import {
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
} from "./action";
import { SMTP_HOST_SENDGRID, SMTP_HOST_MAILGUN } from "./smtp";
import { PLAN_STARTER, PLAN_PRO } from "./stripe";
ACTION_READ_SECRETS
} from './action';
import {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS
} from './smtp';
import { PLAN_STARTER, PLAN_PRO } from './stripe';
import {
MFA_METHOD_EMAIL
} from './user';
import {
TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET
} from './token';
export {
OWNER,
@ -92,6 +105,12 @@ export {
INTEGRATION_OPTIONS,
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS,
PLAN_STARTER,
PLAN_PRO,
MFA_METHOD_EMAIL,
TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET
};

@ -1,7 +1,9 @@
const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net';
const SMTP_HOST_MAILGUN = 'smtp.mailgun.org';
const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com';
export {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS
}

@ -0,0 +1,11 @@
const TOKEN_EMAIL_CONFIRMATION = 'emailConfirmation';
const TOKEN_EMAIL_MFA = 'emailMfa';
const TOKEN_EMAIL_ORG_INVITATION = 'organizationInvitation';
const TOKEN_EMAIL_PASSWORD_RESET = 'passwordReset';
export {
TOKEN_EMAIL_CONFIRMATION,
TOKEN_EMAIL_MFA,
TOKEN_EMAIL_ORG_INVITATION,
TOKEN_EMAIL_PASSWORD_RESET
}

@ -0,0 +1,5 @@
const MFA_METHOD_EMAIL = 'email';
export {
MFA_METHOD_EMAIL
}

@ -7,8 +7,8 @@ require (
github.com/muesli/mango-cobra v1.2.0
github.com/muesli/roff v0.1.0
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.3.0
golang.org/x/term v0.3.0
golang.org/x/crypto v0.6.0
golang.org/x/term v0.5.0
)
require (
@ -31,8 +31,8 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
go.mongodb.org/mongo-driver v1.10.0 // indirect
golang.org/x/net v0.2.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/sys v0.5.0 // indirect
)
require (

@ -106,10 +106,14 @@ go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAV
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -123,9 +127,13 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

@ -128,6 +128,68 @@ func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Req
return secretsResponse, nil
}
func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLoginOneV2Response, error) {
var loginOneV2Response GetLoginOneV2Response
response, err := httpClient.
R().
SetResult(&loginOneV2Response).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/login1", config.INFISICAL_URL))
if err != nil {
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V2: Unsuccessful response: [response=%s]", response)
}
return loginOneV2Response, nil
}
func CallVerifyMfaToken(httpClient *resty.Client, request VerifyMfaTokenRequest) (*VerifyMfaTokenResponse, *VerifyMfaTokenErrorResponse, error) {
var verifyMfaTokenResponse VerifyMfaTokenResponse
var responseError VerifyMfaTokenErrorResponse
response, err := httpClient.
R().
SetResult(&verifyMfaTokenResponse).
SetHeader("User-Agent", USER_AGENT).
SetError(&responseError).
SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/mfa/verify", config.INFISICAL_URL))
if err != nil {
return nil, nil, fmt.Errorf("CallVerifyMfaToken: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return nil, &responseError, nil
}
return &verifyMfaTokenResponse, nil, nil
}
func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLoginTwoV2Response, error) {
var loginTwoV2Response GetLoginTwoV2Response
response, err := httpClient.
R().
SetResult(&loginTwoV2Response).
SetHeader("User-Agent", USER_AGENT).
SetBody(request).
Post(fmt.Sprintf("%v/v2/auth/login2", config.INFISICAL_URL))
if err != nil {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unable to complete api request [err=%s]", err)
}
if response.IsError() {
return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unsuccessful response: [response=%s]", response)
}
return loginTwoV2Response, nil
}
func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesResponse, error) {
var workSpacesResponse GetWorkSpacesResponse
response, err := httpClient.

@ -263,3 +263,63 @@ type GetAccessibleEnvironmentsResponse struct {
IsWriteDenied bool `json:"isWriteDenied"`
} `json:"accessibleEnvironments"`
}
type GetLoginOneV2Request struct {
Email string `json:"email"`
ClientPublicKey string `json:"clientPublicKey"`
}
type GetLoginOneV2Response struct {
ServerPublicKey string `json:"serverPublicKey"`
Salt string `json:"salt"`
}
type GetLoginTwoV2Request struct {
Email string `json:"email"`
ClientProof string `json:"clientProof"`
}
type GetLoginTwoV2Response struct {
MfaEnabled bool `json:"mfaEnabled"`
EncryptionVersion int `json:"encryptionVersion"`
Token string `json:"token"`
PublicKey string `json:"publicKey"`
EncryptedPrivateKey string `json:"encryptedPrivateKey"`
Iv string `json:"iv"`
Tag string `json:"tag"`
ProtectedKey string `json:"protectedKey"`
ProtectedKeyIV string `json:"protectedKeyIV"`
ProtectedKeyTag string `json:"protectedKeyTag"`
}
type VerifyMfaTokenRequest struct {
Email string `json:"email"`
MFAToken string `json:"mfaToken"`
}
type VerifyMfaTokenResponse struct {
EncryptionVersion int `json:"encryptionVersion"`
Token string `json:"token"`
PublicKey string `json:"publicKey"`
EncryptedPrivateKey string `json:"encryptedPrivateKey"`
Iv string `json:"iv"`
Tag string `json:"tag"`
ProtectedKey string `json:"protectedKey"`
ProtectedKeyIV string `json:"protectedKeyIV"`
ProtectedKeyTag string `json:"protectedKeyTag"`
}
type VerifyMfaTokenErrorResponse struct {
Type string `json:"type"`
Message string `json:"message"`
Context struct {
Code string `json:"code"`
TriesLeft int `json:"triesLeft"`
} `json:"context"`
Level int `json:"level"`
LevelName string `json:"level_name"`
StatusCode int `json:"status_code"`
DatetimeIso time.Time `json:"datetime_iso"`
Application string `json:"application"`
Extra []interface{} `json:"extra"`
}

@ -36,9 +36,12 @@ var exportCmd = &cobra.Command{
// util.RequireLocalWorkspaceFile()
},
Run: func(cmd *cobra.Command, args []string) {
envName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err)
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
shouldExpandSecrets, err := cmd.Flags().GetBool("expand")
@ -66,7 +69,7 @@ var exportCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: envName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
if err != nil {
util.HandleError(err, "Unable to fetch secrets")
}

@ -13,7 +13,6 @@ import (
"regexp"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/config"
"github.com/Infisical/infisical-merge/packages/crypto"
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/srp"
@ -23,8 +22,17 @@ import (
"github.com/manifoldco/promptui"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/argon2"
)
type params struct {
memory uint32
iterations uint32
parallelism uint8
saltLength uint32
keyLength uint32
}
// loginCmd represents the login command
var loginCmd = &cobra.Command{
Use: "login",
@ -55,36 +63,146 @@ var loginCmd = &cobra.Command{
util.HandleError(err, "Unable to parse email and password for authentication")
}
userCredentials, err := getFreshUserCredentials(email, password)
loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password)
if err != nil {
log.Infoln("Unable to authenticate with the provided credentials, please try again")
log.Debugln(err)
return
}
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(userCredentials.EncryptedPrivateKey)
tag, err := base64.StdEncoding.DecodeString(userCredentials.Tag)
if err != nil {
util.HandleError(err)
if loginTwoResponse.MfaEnabled {
i := 1
for i < 6 {
mfaVerifyCode := askForMFACode()
httpClient := resty.New()
httpClient.SetAuthToken(loginTwoResponse.Token)
verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{
Email: email,
MFAToken: mfaVerifyCode,
})
if requestError != nil {
util.HandleError(err)
break
} else if mfaErrorResponse != nil {
if mfaErrorResponse.Context.Code == "mfa_invalid" {
msg := fmt.Sprintf("Incorrect, verification code. You have %v attempts left", 5-i)
fmt.Println(msg)
if i == 5 {
util.PrintErrorMessageAndExit("No tries left, please try again in a bit")
break
}
}
if mfaErrorResponse.Context.Code == "mfa_expired" {
util.PrintErrorMessageAndExit("Your 2FA verification code has expired, please try logging in again")
break
}
i++
} else {
loginTwoResponse.EncryptedPrivateKey = verifyMFAresponse.EncryptedPrivateKey
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
loginTwoResponse.Iv = verifyMFAresponse.Iv
loginTwoResponse.ProtectedKey = verifyMFAresponse.ProtectedKey
loginTwoResponse.ProtectedKeyIV = verifyMFAresponse.ProtectedKeyIV
loginTwoResponse.ProtectedKeyTag = verifyMFAresponse.ProtectedKeyTag
loginTwoResponse.PublicKey = verifyMFAresponse.PublicKey
loginTwoResponse.Tag = verifyMFAresponse.Tag
loginTwoResponse.Token = verifyMFAresponse.Token
loginTwoResponse.EncryptionVersion = verifyMFAresponse.EncryptionVersion
break
}
}
}
IV, err := base64.StdEncoding.DecodeString(userCredentials.IV)
if err != nil {
util.HandleError(err)
}
var decryptedPrivateKey []byte
paddedPassword := fmt.Sprintf("%032s", password)
key := []byte(paddedPassword)
if loginTwoResponse.EncryptionVersion == 1 {
encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
if err != nil {
util.HandleError(err)
}
decryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
if err != nil || len(decryptedPrivateKey) == 0 {
util.HandleError(err)
IV, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
if err != nil {
util.HandleError(err)
}
paddedPassword := fmt.Sprintf("%032s", password)
key := []byte(paddedPassword)
decryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV)
if err != nil || len(decryptedPrivateKey) == 0 {
util.HandleError(err)
}
} else if loginTwoResponse.EncryptionVersion == 2 {
protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey)
if err != nil {
util.HandleError(err)
}
protectedKeyTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyTag)
if err != nil {
util.HandleError(err)
}
protectedKeyIV, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyIV)
if err != nil {
util.HandleError(err)
}
nonProtectedTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag)
if err != nil {
util.HandleError(err)
}
nonProtectedIv, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv)
if err != nil {
util.HandleError(err)
}
parameters := &params{
memory: 64 * 1024,
iterations: 3,
parallelism: 1,
keyLength: 32,
}
derivedKey, err := generateFromPassword(password, []byte(loginOneResponse.Salt), parameters)
if err != nil {
util.HandleError(fmt.Errorf("unable to generate argon hash from password [err=%s]", err))
}
decryptedProtectedKey, err := crypto.DecryptSymmetric(derivedKey, protectedKey, protectedKeyTag, protectedKeyIV)
if err != nil {
util.HandleError(fmt.Errorf("unable to get decrypted protected key [err=%s]", err))
}
encryptedPrivateKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey)
if err != nil {
util.HandleError(err)
}
decryptedProtectedKeyInHex, err := hex.DecodeString(string(decryptedProtectedKey))
if err != nil {
util.HandleError(err)
}
decryptedPrivateKey, err = crypto.DecryptSymmetric(decryptedProtectedKeyInHex, encryptedPrivateKey, nonProtectedTag, nonProtectedIv)
if err != nil {
util.HandleError(err)
}
} else {
util.PrintErrorMessageAndExit("Insufficient details to decrypt private key")
}
userCredentialsToBeStored := &models.UserCredentials{
Email: email,
PrivateKey: string(decryptedPrivateKey),
JTWToken: userCredentials.JTWToken,
JTWToken: loginTwoResponse.Token,
}
err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored)
@ -155,7 +273,7 @@ func askForLoginCredentials() (email string, password string, err error) {
return userEmail, userPassword, nil
}
func getFreshUserCredentials(email string, password string) (*api.LoginTwoResponse, error) {
func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2Response, *api.GetLoginTwoV2Response, error) {
log.Debugln("getFreshUserCredentials:", "email", email, "password", password)
httpClient := resty.New()
httpClient.SetRetryCount(5)
@ -166,36 +284,24 @@ func getFreshUserCredentials(email string, password string) (*api.LoginTwoRespon
srpA := hex.EncodeToString(srpClient.ComputeA())
// ** Login one
loginOneRequest := api.LoginOneRequest{
loginOneResponseResult, err := api.CallLogin1V2(httpClient, api.GetLoginOneV2Request{
Email: email,
ClientPublicKey: srpA,
}
var loginOneResponseResult api.LoginOneResponse
loginOneResponse, err := httpClient.
R().
SetBody(loginOneRequest).
SetResult(&loginOneResponseResult).
Post(fmt.Sprintf("%v/v1/auth/login1", config.INFISICAL_URL))
})
if err != nil {
return nil, err
}
if loginOneResponse.StatusCode() > 299 {
return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", loginOneResponse)
util.HandleError(err)
}
// **** Login 2
serverPublicKey_bytearray, err := hex.DecodeString(loginOneResponseResult.ServerPublicKey)
if err != nil {
return nil, err
return nil, nil, err
}
userSalt, err := hex.DecodeString(loginOneResponseResult.ServerSalt)
userSalt, err := hex.DecodeString(loginOneResponseResult.Salt)
if err != nil {
return nil, err
return nil, nil, err
}
srpClient.SetSalt(userSalt, []byte(email), []byte(password))
@ -203,27 +309,16 @@ func getFreshUserCredentials(email string, password string) (*api.LoginTwoRespon
srpM1 := srpClient.ComputeM1()
LoginTwoRequest := api.LoginTwoRequest{
loginTwoResponseResult, err := api.CallLogin2V2(httpClient, api.GetLoginTwoV2Request{
Email: email,
ClientProof: hex.EncodeToString(srpM1),
}
var loginTwoResponseResult api.LoginTwoResponse
loginTwoResponse, err := httpClient.
R().
SetBody(LoginTwoRequest).
SetResult(&loginTwoResponseResult).
Post(fmt.Sprintf("%v/v1/auth/login2", config.INFISICAL_URL))
})
if err != nil {
return nil, err
util.HandleError(err)
}
if loginTwoResponse.StatusCode() > 299 {
return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", loginTwoResponse)
}
return &loginTwoResponseResult, nil
return &loginOneResponseResult, &loginTwoResponseResult, nil
}
func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) {
@ -237,3 +332,21 @@ func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) {
}
return result == "Yes", err
}
func generateFromPassword(password string, salt []byte, p *params) (hash []byte, err error) {
hash = argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
return hash, nil
}
func askForMFACode() string {
mfaCodePromptUI := promptui.Prompt{
Label: "Enter the 2FA verification code sent to your email",
}
mfaVerifyCode, err := mfaCodePromptUI.Run()
if err != nil {
util.HandleError(err)
}
return mfaVerifyCode
}

@ -54,9 +54,12 @@ var runCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
envName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err, "Unable to parse flag")
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
infisicalToken, err := cmd.Flags().GetString("token")
@ -79,7 +82,7 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: envName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
if err != nil {
util.HandleError(err, "Could not fetch secrets", "If you are using a service token to fetch secrets, please ensure it is valid")

@ -19,7 +19,6 @@ import (
"github.com/Infisical/infisical-merge/packages/util"
"github.com/Infisical/infisical-merge/packages/visualize"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@ -31,9 +30,12 @@ var secretsCmd = &cobra.Command{
PreRun: toggleDebug,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
environmentName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err)
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
infisicalToken, err := cmd.Flags().GetString("token")
@ -94,9 +96,12 @@ var secretsSetCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
util.RequireLocalWorkspaceFile()
environmentName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err, "Unable to parse flag")
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
workspaceFile, err := util.GetWorkSpaceFromFile()
@ -270,11 +275,12 @@ var secretsDeleteCmd = &cobra.Command{
PreRun: toggleDebug,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
environmentName, err := cmd.Flags().GetString("env")
if err != nil {
log.Errorln("Unable to parse the environment name flag")
log.Debugln(err)
return
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
@ -330,9 +336,12 @@ var secretsDeleteCmd = &cobra.Command{
}
func getSecretsByNames(cmd *cobra.Command, args []string) {
environmentName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err, "Unable to parse flag")
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
infisicalToken, err := cmd.Flags().GetString("token")
@ -373,9 +382,12 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
}
func generateExampleEnv(cmd *cobra.Command, args []string) {
environmentName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err, "Unable to parse flag")
environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvelopmentBasedOnGitBranch()
if environmentFromWorkspace != "" {
environmentName = environmentFromWorkspace
}
}
infisicalToken, err := cmd.Flags().GetString("token")
@ -394,6 +406,11 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
}
tagsHashToSecretKey := make(map[string]int)
slugsToFilerBy := make(map[string]int)
for _, slug := range strings.Split(tagSlugs, ",") {
slugsToFilerBy[slug] = 1
}
type TagsAndSecrets struct {
Secrets []models.SingleEnvironmentVariable
@ -410,6 +427,25 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
return len(secrets[i].Tags) > len(secrets[j].Tags)
})
for i, secret := range secrets {
filteredTag := []struct {
ID string "json:\"_id\""
Name string "json:\"name\""
Slug string "json:\"slug\""
Workspace string "json:\"workspace\""
}{}
for _, secretTag := range secret.Tags {
_, exists := slugsToFilerBy[secretTag.Slug]
if !exists {
filteredTag = append(filteredTag, secretTag)
}
}
secret.Tags = filteredTag
secrets[i] = secret
}
for _, secret := range secrets {
listOfTagSlugs := []string{}
@ -473,6 +509,8 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
return len(listOfsecretDetails[i].Tags) < len(listOfsecretDetails[j].Tags)
})
tableOfContents := []string{}
fullyGeneratedDocuments := []string{}
for _, secretDetails := range listOfsecretDetails {
listOfKeyValue := []string{}
@ -513,11 +551,22 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
heading := CenterString(strings.Join(listOfTagNames, " & "), 80)
if len(listOfTagNames) == 0 {
fmt.Printf("\n%s \n", strings.Join(listOfKeyValue, "\n \n"))
fullyGeneratedDocuments = append(fullyGeneratedDocuments, fmt.Sprintf("\n%s \n", strings.Join(listOfKeyValue, "\n")))
} else {
fmt.Printf("\n\n\n%s \n%s \n", heading, strings.Join(listOfKeyValue, "\n \n"))
fullyGeneratedDocuments = append(fullyGeneratedDocuments, fmt.Sprintf("\n\n\n%s \n%s \n", heading, strings.Join(listOfKeyValue, "\n")))
tableOfContents = append(tableOfContents, strings.ToUpper(strings.Join(listOfTagNames, " & ")))
}
}
dashedList := []string{}
for _, item := range tableOfContents {
dashedList = append(dashedList, fmt.Sprintf("# - %s \n", item))
}
if len(dashedList) > 0 {
fmt.Println(CenterString("TABLE OF CONTENTS", 80))
fmt.Println(strings.Join(dashedList, ""))
}
fmt.Println(strings.Join(fullyGeneratedDocuments, ""))
}
func CenterString(s string, numStars int) string {

@ -39,7 +39,9 @@ type Workspace struct {
}
type WorkspaceConfigFile struct {
WorkspaceId string `json:"workspaceId"`
WorkspaceId string `json:"workspaceId"`
DefaultEnvironment string `json:"defaultEnvironment"`
GitBranchToEnvironmentMapping map[string]string `json:"gitBranchToEnvironmentMapping"`
}
type SymmetricEncryptionResult struct {
@ -49,7 +51,8 @@ type SymmetricEncryptionResult struct {
}
type GetAllSecretsParameters struct {
Environment string
InfisicalToken string
TagSlugs string
Environment string
EnvironmentPassedViaFlag bool
InfisicalToken string
TagSlugs string
}

@ -1,10 +1,14 @@
package util
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
"os/exec"
"path"
"strings"
)
type DecodedSymmetricEncryptionDetails = struct {
@ -110,3 +114,14 @@ func GetHashFromStringList(list []string) string {
sum := sha256.Sum256(hash.Sum(nil))
return fmt.Sprintf("%x", sum)
}
func getCurrentBranch() (string, error) {
cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", err
}
return path.Base(strings.TrimSpace(out.String())), nil
}

@ -481,3 +481,27 @@ func DeleteBackupSecrets() error {
return os.RemoveAll(fullPathToSecretsBackupFolder)
}
func GetEnvelopmentBasedOnGitBranch() string {
branch, err := getCurrentBranch()
if err != nil {
log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
}
workspaceFile, err := GetWorkSpaceFromFile()
if err != nil {
log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
return ""
}
envBasedOnGitBranch, ok := workspaceFile.GitBranchToEnvironmentMapping[branch]
log.Debugf("GetEnvelopmentBasedOnGitBranch: [envBasedOnGitBranch=%s] [ok=%s]", envBasedOnGitBranch, ok)
if err == nil && ok {
return envBasedOnGitBranch
} else {
log.Debugf("getEnvelopmentBasedOnGitBranch: [err=%s]", err)
return ""
}
}

@ -5,8 +5,6 @@ description: "How to sync your secrets among various 3rd-party services with Inf
Integrations allow environment variables to be synced across your entire infrastructure from local development to CI/CD and production.
We're still relatively early with integrations. 6+ integrations are already avaiable but expect more coming very soon.
<Card title="View integrations" icon="link" href="/integrations/overview">
View all available integrations and their guides
</Card>

@ -0,0 +1,18 @@
---
title: "MFA"
description: "Secure your Infisical account with MFA"
---
MFA requires users to provide multiple forms of identification to access their account. Currently, this means logging in with your password and a 6-digit code sent to your email.
## Email 2FA
Check the box in Personal Settings > Two-factor Authentication to enable email-based 2FA.
![Email-based MFA](../../images/mfa-email.png)
<Note>
Infisical currently supports email-based 2FA. We're actively working on
building support for other forms of identification via SMS and Authenticator
App.
</Note>

Binary file not shown.

After

(image error) Size: 315 KiB

Binary file not shown.

After

(image error) Size: 468 KiB

Binary file not shown.

After

(image error) Size: 332 KiB

Binary file not shown.

After

(image error) Size: 162 KiB

Binary file not shown.

After

(image error) Size: 180 KiB

Binary file not shown.

After

(image error) Size: 176 KiB

Binary file not shown.

After

(image error) Size: 339 KiB

Binary file not shown.

Before

(image error) Size: 421 KiB

After

(image error) Size: 385 KiB

BIN
docs/images/mfa-email.png Normal file

Binary file not shown.

After

(image error) Size: 236 KiB

@ -1,5 +1,36 @@
---
title: "Circle CI"
description: "How to automatically sync secrets from Infisical into your CircleCI project."
---
Coming soon.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for CircleCI
Obtain an API token in User Settings > Personal API Tokens
![integrations circleci token](../../images/integrations-circleci-token.png)
Press on the CircleCI tile and input your CircleCI API token to grant Infisical access to your CircleCI account.
![integrations circleci authorization](../../images/integrations-circleci-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which CircleCI project and press create integration to start syncing secrets to CircleCI.
![integrations fly](../../images/integrations-circleci-create.png)
![integrations fly](../../images/integrations-circleci.png)

@ -21,6 +21,8 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
| [React](/integrations/frameworks/react) | Framework | Available |
| [Vue](/integrations/frameworks/vue) | Framework | Available |
| [Express](/integrations/frameworks/express) | Framework | Available |
@ -39,8 +41,6 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| GCP | Cloud | Coming soon |
| Azure | Cloud | Coming soon |
| DigitalOcean | Cloud | Coming soon |
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon |
| TravisCI | CI/CD | Coming soon |
| GitHub Actions | CI/CD | Coming soon |
| Jenkins | CI/CD | Coming soon |

@ -98,6 +98,7 @@
"getting-started/dashboard/pit-recovery",
"getting-started/dashboard/secret-versioning",
"getting-started/dashboard/audit-logs",
"getting-started/dashboard/mfa",
"getting-started/dashboard/token"
]
},

@ -7,22 +7,50 @@ Infisical stores a range of data namely user, secrets, keys, organization, proje
## Users
The `User` model includes the fields `email`, `firstName`, `lastName`, `publicKey`, `encryptedPrivateKey`, `iv`, `tag`, `salt`, `verifier`, and `refreshVersion`.
The `User` model includes the fields `email`, `firstName`, `lastName`, `publicKey`, `encryptionVersion`, `protectedKey`, `protectedKeyIV`, `protectedKeyTag`, `encryptedPrivateKey`, `iv`, `tag`, `salt`, `verifier`, and `refreshVersion`.
Infisical makes a usability-security tradeoff to give users convenient access to public-private key pairs across different devices upon login, solving key-storage and transfer challenges across device and browser mediums, in exchange for it storing `encryptedPrivateKey`. In any case, private keys are symmetrically encrypted locally by user passwords which are not sent to the server — this is done with SRP.
Infisical makes a usability-security tradeoff that is to give users convenient access to public-private key pairs across different devices upon login, solving key-storage and transfer challenges across device and browser mediums, in exchange for it storing `encryptedPrivateKey`.
<Note>
`encryptedPrivateKey` is obtained by symmetrically encrypting the user's
private key locally with a protected key which is encrypted by the key derived
from the user's password and salt. Encryption is done via `AES256-GCM` and key
derivation via `argon2id`. The user's password is not sent to the server —
this is done with SRP.
</Note>
## Secrets
The `Secret` model includes the fields `workspace`, `type`, `user`, `environment`, `secretKeyCiphertext`, `secretKeyIV`, `secretKeyTag`, `secretKeyHash`, `secretValueCiphertext`, `secretValueIV`, `secretValueTag`, and `secretValueHash`.
The `Secret` model includes the fields `workspace`, `type`, `user`, `environment`, `secretKeyCiphertext`, `secretKeyIV`, `secretKeyTag`, `secretValueCiphertext`, `secretValueIV`, and `secretValueTag`.
Each secret is symmetrically encrypted by the key of the project that it belongs to; that key's encrypted copies are stored in a separate `Key` collection.
## Keys
## Project Keys
The `Key` model includes the fields `encryptedKey`, `nonce`, `sender`, `receiver`, and `workspace`.
Infisical stores copies of project keys, one for each member of a project, encrypted under each member's public key.
## Bots
The `Bot` model contains the fields `name`, `workspace`, `isActive`, `publicKey`, `encryptedPrivateKey`, `iv`, and `tag`.
Each project comes with a bot that has its own public-private key pair; its private key is encrypted by the server's symmetric key. If needed, a user can opt-in to share their project key with the bot (i.e. Infisical) to give the platform access to the project's secrets.
<Note>
Sharing secrets with Infisical so they can be synced to integrations like
Vercel, GitHub, and Netlify is something we make sure users consent to before
opting in.
</Note>
## Organizations and Workspaces
The `Organization`, `Workspace`, `MembershipOrg`, and `Membership` models contain enrollment information for organizations and projects; they are used to check if users are authorized to retrieve select secrets.
## Service Tokens
The `ServiceTokenData` model contains data for service tokens that enable users to fetch secrets from a particular project and environment; each service token data record includes an (encrypted) copy of the project key that it is bound to as well as a validation hash for `bcrypt`.
## API Keys
The `APIKeyData` model contains data for API keys that enable users to interact with [Infisical's Open API](https://infisical.com/docs/api-reference/overview/introduction); each API key data record includes a validation hash for `bcrypt`.

@ -5,7 +5,11 @@ description: "Quick explanation of how Infisical works."
## Signup
During account signup, a user confirms their email address via OTP, generates a public-private key pair to be stored locally (private keys are symmetrically encrypted by the user's newly-made password), and forwards SRP-related values and user identifier information to the server. This includes `email`, `firstName`, `lastName`, `publicKey`, `encryptedPrivateKey`, `iv`, `tag`, `salt`, `verifier`, and `organizationName`.
During account signup, a user confirms their email address via OTP, generates a public-private key pair to be stored locally, generates a user salt, generates a 256-bit key, and enters their password.
The 256-bit key is used to encrypt the private key; the 256-bit key itself is then encrypted by a key generated from the user's password and salt with key derivation function `argon2id`. The resulting, 256-bit key the protected key.
The encrypted private key, protected key, user identifier information, and SRP details are forwarded to the server.
Once authenticated via SRP, a user is issued a JWT and refresh token. The JWT token is stored in browser memory under a write-only class `SecurityClient` that appends the token to all future outbound requests requiring authentication. The refresh token is stored in an `HttpOnly` cookie and included in future requests to `/api/token` for JWT token renewal. This design side-steps potential XSS attacks on local storage.

@ -25,7 +25,8 @@ By default, you need to configure the following SMTP [environment variables](htt
Below you will find details on how to configure common email providers (not in any particular order).
## Twilio SendGrid
<AccordionGroup>
<Accordion title="Twilio SendGrid">
1. Create an account and configure [SendGrid](https://sendgrid.com) to send emails.
2. Create a SendGrid API Key under Settings > [API Keys](https://app.sendgrid.com/settings/api_keys)
@ -47,11 +48,12 @@ SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out em
SMTP_FROM_NAME=Infisical
```
<Info>
Remember that you will need to restart Infisical for this to work properly.
</Info>
<Info>
Remember that you will need to restart Infisical for this to work properly.
</Info>
</Accordion>
## Mailgun
<Accordion title="Mailgun">
1. Create an account and configure [Mailgun](https://www.mailgun.com) to send emails.
2. Obtain your Mailgun credentials in Sending > Overview > SMTP
@ -70,7 +72,9 @@ SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out em
SMTP_FROM_NAME=Infisical
```
## AWS SES
</Accordion>
<Accordion title="AWS SES">
1. Create an account and [configure AWS SES](https://aws.amazon.com/premiumsupport/knowledge-center/ses-set-up-connect-smtp/) to send emails in the Amazon SES console.
2. Create an IAM user for SMTP authentication and obtain SMTP credentials in SMTP settings > Create SMTP credentials
@ -82,7 +86,6 @@ SMTP_FROM_NAME=Infisical
3. With your AWS SES SMTP credentials, you can now set up your SMTP environment variables:
```
SMTP_HOST=smtp.mailgun.org # obtained from credentials page
SMTP_HOST=email-smtp.ap-northeast-1.amazonaws.com # SMTP endpoint obtained from SMTP settings
SMTP_USERNAME=xxx # your SMTP username
SMTP_PASSWORD=xxx # your SMTP password
@ -95,3 +98,40 @@ SMTP_FROM_NAME=Infisical
<Info>
Remember that you will need to restart Infisical for this to work properly.
</Info>
</Accordion>
<Accordion title="SocketLabs">
1. Create an account and configure [SocketLabs](https://www.socketlabs.com/) to send emails.
2. From the dashboard, navigate to SMTP Credentials > SMTP & APIs > SMTP Credentials to obtain your SocketLabs SMTP credentials.
![opening SocketLabs dashboard](../../images/email-socketlabs-dashboard.png)
![obtaining SocketLabs credentials](../../images/email-socketlabs-credentials.png)
3. With your SocketLabs SMTP credentials, you can now set up your SMTP environment variables:
```
SMTP_HOST=smtp.socketlabs.com
SMTP_USERNAME=username # obtained from your credentials
SMTP_PASSWORD=password # obtained from your credentials
SMTP_PORT=587
SMTP_SECURE=true
SMTP_FROM_ADDRESS=hey@example.com # your email address being used to send out emails
SMTP_FROM_NAME=Infisical
```
<Note>
The `SMTP_FROM_ADDRESS` environment variable should be an email for an
authenticated domain under Configuration > Domain Management in SocketLabs.
For example, if you're using SocketLabs in sandbox mode, then you may use an
email like `team@sandbox.socketlabs.dev`.
</Note>
![SocketLabs domain management](../../images/email-socketlabs-domains.png)
<Info>
Remember that you will need to restart Infisical for this to work properly.
</Info>
</Accordion>
</AccordionGroup>

@ -5,38 +5,40 @@ description: "How to configure your environment variables when self-hosting Infi
Configuring Infisical requires setting some environment variables. There is a file called [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) at the root directory of our main repo that you can use to create a `.env` file before you start the server.
| Variable | Description | Default Value |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------- |
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` |
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
| `EMAIL_TOKEN_LIFETIME` | Email OTP/magic-link lifetime expressed in seconds | `86400` |
| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` |
| `MONGO_USERNAME` | MongoDB username if using container | `None` |
| `MONGO_PASSWORD` | MongoDB password if using container | `None` |
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` |
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
| `SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `team@infisical.com`) | `None` |
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
| `LICENSE_KEY` | License key if using Infisical Enterprise Edition | `true` |
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` |
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` |
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` |
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` |
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` |
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
| `INVITE_ONLY_SIGNUP` | If true, users can only sign up if they are invited | `false` |
| Variable | Description | Default Value |
| ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_MFA_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` |
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` |
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
| `JWT_MFA_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `5m` |
| `EMAIL_TOKEN_LIFETIME` | Email OTP/magic-link lifetime expressed in seconds | `86400` |
| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` |
| `MONGO_USERNAME` | MongoDB username if using container | `None` |
| `MONGO_PASSWORD` | MongoDB password if using container | `None` |
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` |
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
| `SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `team@infisical.com`) | `None` |
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
| `LICENSE_KEY` | License key if using Infisical Enterprise Edition | `true` |
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` |
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` |
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` |
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` |
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` |
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
| `INVITE_ONLY_SIGNUP` | If true, users can only sign up if they are invited | `false` |

@ -24,114 +24,88 @@ Before you can deploy the Helm chart, you must fill out the required environment
Refer to the available [environment variables](../../self-hosting/configuration/envars) to learn more
<Accordion title="values.yaml">
[View all available Helm chart values parameters](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical)
```yaml
#####
# INFISICAL K8 DEFAULT VALUES FILE
# PLEASE REPLACE VALUES/EDIT AS REQUIRED
#####
nameOverride: ""
frontend:
enabled: true
name: frontend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 2
image:
repository: infisical/frontend
pullPolicy: Always
tag: "latest" # It it highly recommended to select a specific tag for prod deployment so it is easy to rollback: https://hub.docker.com/r/infisical/frontend/tags
# kubeSecretRef: some-kube-secret-name
tag: "latest"
pullPolicy: IfNotPresent
kubeSecretRef: ""
service:
# type of the frontend service
type: ClusterIP
# define the nodePort if service type is NodePort
# nodePort:
annotations: {}
type: ClusterIP
nodePort: ""
frontendEnvironmentVariables:
SITE_URL: infisical.local
backend:
enabled: true
name: backend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 2
image:
repository: infisical/backend
pullPolicy: Always
tag: "latest" # It it highly recommended to select a specific tag for prod deployment so it is easy to rollback: https://hub.docker.com/r/infisical/backend/tags
# kubeSecretRef: some-kube-secret-name
service:
annotations: {}
mongodb:
name: mongodb
podAnnotations: {}
image:
repository: mongo
tag: "latest"
pullPolicy: IfNotPresent
tag: "latest"
kubeSecretRef: ""
service:
annotations: {}
type: ClusterIP
nodePort: ""
# By default the backend will be connected to a Mongo instance in the cluster.
# However, it is recommended to add a managed document DB connection string because the DB instance in the cluster does not have persistence yet ( data will be deleted on next deploy).
# Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
mongodbConnection: {}
# externalMongoDBConnectionString: <>
backendEnvironmentVariables:
ENCRYPTION_KEY: MUST_REPLACE
JWT_SIGNUP_SECRET: MUST_REPLACE
JWT_REFRESH_SECRET: MUST_REPLACE
JWT_AUTH_SECRET: MUST_REPLACE
JWT_SERVICE_SECRET: MUST_REPLACE
SMTP_HOST: MUST_REPLACE
SMTP_PORT: 587
SMTP_SECURE: false
SMTP_FROM_NAME: Infisical
SMTP_FROM_ADDRESS: MUST_REPLACE
SMTP_USERNAME: MUST_REPLACE
SMTP_PASSWORD: MUST_REPLACE
SITE_URL: infisical.local
## Mongo DB persistence
mongodb:
enabled: true
## By default the backend will be connected to a Mongo instance within the cluster
## However, it is recommended to add a managed document DB connection string for production-use (DBaaS)
## Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
## e.g. "mongodb://<user>:<pass>@<host>:<port>/<database-name>"
mongodbConnection:
externalMongoDBConnectionString: ""
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
hostName: example.com # replace with your domain
frontend:
# cert-manager.io/issuer: letsencrypt-nginx
hostName: infisical.local ## <- Replace with your own domain
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls: []
# - secretName: letsencrypt-nginx
# hosts:
# - infisical.local
## Complete Ingress example
# ingress:
# enabled: true
# annotations:
# kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx
# hostName: k8.infisical.com
# frontend:
# path: /
# pathType: Prefix
# backend:
# path: /api
# pathType: Prefix
# tls:
# - secretName: letsencrypt-nginx
# hosts:
# - k8.infisical.com
###
### YOU MUST FILL IN ALL SECRETS BELOW
###
backendEnvironmentVariables:
# Required keys for platform encryption/decryption ops. Replace with nacl sk keys
ENCRYPTION_KEY: MUST_REPLACE
# JWT
# Required secrets to sign JWT tokens
JWT_SIGNUP_SECRET: MUST_REPLACE
JWT_REFRESH_SECRET: MUST_REPLACE
JWT_AUTH_SECRET: MUST_REPLACE
# Mail/SMTP
# Required to send emails
SMTP_HOST: MUST_REPLACE
SMTP_NAME: MUST_REPLACE
SMTP_USERNAME: MUST_REPLACE
SMTP_PASSWORD: MUST_REPLACE
frontendEnvironmentVariables: {}
mailhog:
enabled: false
```
</Accordion>

@ -1,4 +1,9 @@
module.exports = {
overrides: [
{
files: ["next.config.js"]
}
],
root: true,
env: {
browser: true,
@ -87,6 +92,7 @@ module.exports = {
}
]
},
ignorePatterns: ['next.config.js'],
settings: {
'import/resolver': {
typescript: {

@ -10,6 +10,6 @@ export const parameters = {
}
},
darkMode: {
dark: { ...themes.dark, appContentBg: '#0e1014', appBg: '#0e1014' }
dark: { ...themes.dark, appContentBg: 'rgb(14,16,20)', appBg: 'rgb(14,16,20)' }
}
};

@ -1,9 +1,11 @@
// @ts-check
/**
* @type {import('next').NextConfig}
**/
const { i18n } = require("./next-i18next.config.js");
const path = require('path');
const ContentSecurityPolicy = `
default-src 'self';
@ -65,7 +67,33 @@ module.exports = {
},
];
},
webpack: (config, { isServer, webpack }) => {
webpack: (config, { isServer, webpack }) => { // config
config.module.rules.push({
test: /\.wasm$/,
loader: "base64-loader",
type: "javascript/auto",
});
config.module.noParse = /\.wasm$/;
config.module.rules.forEach((rule) => {
(rule.oneOf || []).forEach((oneOf) => {
if (oneOf.loader && oneOf.loader.indexOf("file-loader") >= 0) {
oneOf.exclude.push(/\.wasm$/);
}
});
});
if (!isServer) {
config.resolve.fallback.fs = false;
}
// Perform customizations to webpack config
config.plugins.push(
new webpack.IgnorePlugin({ resourceRegExp: /\/__tests__\// })
);
// Important: return the modified config
return config;
},
i18n,

@ -27,14 +27,18 @@
"@radix-ui/react-progress": "^1.0.1",
"@radix-ui/react-select": "^1.2.0",
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-toast": "^1.1.2",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.46.0",
"@tanstack/react-query": "^4.23.0",
"@types/argon2-browser": "^1.18.1",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
"cva": "npm:class-variance-authority@^0.4.0",
@ -2338,54 +2342,6 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
"integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
},
"node_modules/@esbuild/android-arm": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
@ -2402,294 +2358,6 @@
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
"integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@eslint/eslintrc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@ -3292,36 +2960,6 @@
"integrity": "sha512-NXGXGFGiOKEnvBIHq9cdFTKbHO2/4B3Zd9K27M7j1DioIQVar7oVRqZMYs0h3XMVEZLwjjkdAtqRPCzzd3RtXg==",
"dev": true
},
"node_modules/@next/swc-android-arm-eabi": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz",
"integrity": "sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-android-arm64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz",
"integrity": "sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz",
@ -3337,156 +2975,6 @@
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz",
"integrity": "sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-freebsd-x64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz",
"integrity": "sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm-gnueabihf": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz",
"integrity": "sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz",
"integrity": "sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz",
"integrity": "sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz",
"integrity": "sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz",
"integrity": "sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz",
"integrity": "sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz",
"integrity": "sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz",
"integrity": "sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -4271,6 +3759,26 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-tabs": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.2.tgz",
"integrity": "sha512-gOUwh+HbjCuL0UCo8kZ+kdUEG8QtpdO4sMQduJ34ZEz0r4922g9REOBM+vIsfwtGxSug4Yb1msJMJYN2Bk8TpQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-direction": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-roving-focus": "1.0.2",
"@radix-ui/react-use-controllable-state": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-toast": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.2.tgz",
@ -6935,6 +6443,11 @@
"@testing-library/dom": ">=7.21.4"
}
},
"node_modules/@types/argon2-browser": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.1.tgz",
"integrity": "sha512-PZffP/CqH9m2kovDSRQMfMMxUC3V98I7i7/caa0RB0/nvsXzYbL9bKyqZpNMFmLFGZslROlG1R60ONt7abrwlA=="
},
"node_modules/@types/aria-query": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
@ -8343,6 +7856,11 @@
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
},
"node_modules/argon2-browser": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
"integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw=="
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -8937,6 +8455,11 @@
}
]
},
"node_modules/base64-loader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz",
"integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA=="
},
"node_modules/before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
@ -24279,27 +23802,6 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz",
"integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg=="
},
"@esbuild/android-arm": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
@ -24307,132 +23809,6 @@
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
"integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
"dev": true,
"optional": true
},
"@eslint/eslintrc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz",
@ -24883,84 +24259,12 @@
"integrity": "sha512-NXGXGFGiOKEnvBIHq9cdFTKbHO2/4B3Zd9K27M7j1DioIQVar7oVRqZMYs0h3XMVEZLwjjkdAtqRPCzzd3RtXg==",
"dev": true
},
"@next/swc-android-arm-eabi": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz",
"integrity": "sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==",
"optional": true
},
"@next/swc-android-arm64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz",
"integrity": "sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==",
"optional": true
},
"@next/swc-darwin-arm64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz",
"integrity": "sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA==",
"optional": true
},
"@next/swc-darwin-x64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz",
"integrity": "sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==",
"optional": true
},
"@next/swc-freebsd-x64": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz",
"integrity": "sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==",
"optional": true
},
"@next/swc-linux-arm-gnueabihf": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz",
"integrity": "sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==",
"optional": true
},
"@next/swc-linux-arm64-gnu": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz",
"integrity": "sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==",
"optional": true
},
"@next/swc-linux-arm64-musl": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz",
"integrity": "sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==",
"optional": true
},
"@next/swc-linux-x64-gnu": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz",
"integrity": "sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==",
"optional": true
},
"@next/swc-linux-x64-musl": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz",
"integrity": "sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==",
"optional": true
},
"@next/swc-win32-arm64-msvc": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz",
"integrity": "sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==",
"optional": true
},
"@next/swc-win32-ia32-msvc": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz",
"integrity": "sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==",
"optional": true
},
"@next/swc-win32-x64-msvc": {
"version": "12.3.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz",
"integrity": "sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==",
"optional": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -25552,6 +24856,22 @@
"@radix-ui/react-use-size": "1.0.0"
}
},
"@radix-ui/react-tabs": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.0.2.tgz",
"integrity": "sha512-gOUwh+HbjCuL0UCo8kZ+kdUEG8QtpdO4sMQduJ34ZEz0r4922g9REOBM+vIsfwtGxSug4Yb1msJMJYN2Bk8TpQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-direction": "1.0.0",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-roving-focus": "1.0.2",
"@radix-ui/react-use-controllable-state": "1.0.0"
}
},
"@radix-ui/react-toast": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.2.tgz",
@ -27446,6 +26766,11 @@
"@babel/runtime": "^7.12.5"
}
},
"@types/argon2-browser": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/@types/argon2-browser/-/argon2-browser-1.18.1.tgz",
"integrity": "sha512-PZffP/CqH9m2kovDSRQMfMMxUC3V98I7i7/caa0RB0/nvsXzYbL9bKyqZpNMFmLFGZslROlG1R60ONt7abrwlA=="
},
"@types/aria-query": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.1.tgz",
@ -28577,6 +27902,11 @@
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true
},
"argon2-browser": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
"integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw=="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@ -29012,6 +28342,11 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"base64-loader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base64-loader/-/base64-loader-1.0.0.tgz",
"integrity": "sha512-p32+F8dg+ANGx7s8QsZS74ZPHfIycmC2yZcoerzFgbersIYWitPbbF39G6SBx3gyvzyLH5nt1ooocxr0IHuWKA=="
},
"before-after-hook": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",

@ -34,14 +34,18 @@
"@radix-ui/react-progress": "^1.0.1",
"@radix-ui/react-select": "^1.2.0",
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-toast": "^1.1.2",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/stripe-js": "^1.46.0",
"@types/argon2-browser": "^1.18.1",
"@tanstack/react-query": "^4.23.0",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
"cva": "npm:class-variance-authority@^0.4.0",

@ -13,6 +13,7 @@ export interface SecretDataProps {
value: string | undefined;
valueOverride: string | undefined;
id: string;
idOverride?: string;
comment: string;
tags: Tag[];
}

@ -0,0 +1,235 @@
<svg width="1096" height="1024" viewBox="0 0 1096 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M652.84 516.37L655.57 496.81H661.39L663.89 486.77H667.21L669.61 476.51C669.61 476.51 712.12 478.93 749.44 484.55C786.76 490.17 826.43 499.87 826.43 499.87C826.43 499.87 830.12 453.85 831.88 427.3C833.63 400.75 841.09 361.75 841.09 361.75L847.03 381.57L854.24 352.91L867.19 387.39C867.19 387.39 867.68 364.88 871.36 357.72C875.04 350.56 875.72 339.86 876.39 341.44C877.06 343.02 902.81 414.74 920.98 445.7C939.15 476.66 947.96 494.14 947.96 494.14C947.96 494.14 955.48 504.33 958.31 510.78C961.14 517.24 959.89 522.41 958.92 525.31C957.95 528.21 958 533.77 950.07 540.91C942.15 548.04 933.07 549.85 879.61 540.42C826.15 530.99 774.34 526.59 742.93 524.83C711.52 523.07 652.86 516.38 652.86 516.38L652.84 516.37Z" fill="#E5F7B7"/>
<path d="M655.09 514.47C655.09 514.47 698.6 509.96 773.53 518.61C848.46 527.26 890.16 538.4 912.52 540.9C934.88 543.41 944.76 538.47 944.76 538.47C944.76 538.47 949.79 536.8 952.68 532.69C955.57 528.58 956.14 523.64 956.27 521.98C956.4 520.31 956.68 518.69 956.27 517.45C955.86 516.21 952.45 510.6 951.59 509.6C950.73 508.6 950.12 507.13 950.12 507.13C950.12 507.13 953.73 510.22 956.48 514.16C959.23 518.1 959.39 523.39 958.89 525.3C958.39 527.21 958.15 540.12 943.24 545.98C933.61 549.76 917.62 547.09 871.8 539.07C825.98 531.05 793.78 527.77 777.34 526.55C760.9 525.33 728.21 522.38 713.15 521.1C698.09 519.82 655.08 514.46 655.08 514.46L655.09 514.47Z" fill="#B4BF8F"/>
<path d="M702.2 888.02C702.2 888.02 576.88 875.18 575.97 874.24C575.06 873.29 573.81 874.27 575.24 866.6C576.67 858.93 610.77 685.18 616.71 641.48C622.65 597.78 636.23 541.36 638.48 529.91C640.73 518.46 639.62 515.85 647.67 516.03C655.72 516.21 739.33 524.73 773.75 527.61C808.17 530.49 894.49 543.5 912.38 546.01C930.27 548.52 945.04 547.55 952.5 541.21C959.96 534.87 965.23 526.39 965.59 517.28C965.95 508.17 962.71 497.6 962.71 497.6L967.15 493.31C967.15 493.31 974.07 505.43 968.81 526.58C963.55 547.72 920.27 678.91 896.02 778.85C871.78 878.79 863.96 894.74 864.14 899.4C864.32 904.06 858.14 909.92 843.14 910.51C828.14 911.09 823.58 910.18 819.72 905.03C815.86 899.88 702.18 888.02 702.18 888.02H702.2Z" fill="url(#paint0_linear_121_754)"/>
<path d="M950.06 540.9C956.45 534.8 957.48 529.26 958.91 525.3C960.34 521.35 960.29 514.3 957.21 508.41C955.51 505.15 938.98 479.83 927.55 458.57C916.12 437.31 895.58 391.31 895.58 391.31C895.58 391.31 906.51 352.41 907.19 350.81C907.87 349.21 909.75 353.7 912.65 362.23C915.55 370.76 936.81 423.13 943.46 437.88C950.11 452.63 955.33 462.79 955.33 462.79C955.33 462.79 961.09 491.47 963.53 500.62C965.97 509.76 967.4 518.52 964 525.31C960.6 532.1 960.14 533.92 956.32 537.56C952.5 541.2 950.05 540.9 950.05 540.9H950.06Z" fill="url(#paint1_linear_121_754)"/>
<path d="M940 480.33C940 480.33 955.17 462.92 963.15 453.35C971.13 443.77 978.18 441.09 985.36 441.48C992.54 441.87 999.94 449.97 1008.96 464.17C1017.98 478.37 1021.23 482.04 1021.23 482.04C1021.23 482.04 1012.53 469.81 998.42 470.88C984.31 471.95 978.66 474.74 974.5 482.04C970.33 489.33 956.32 506.78 956.32 506.78L940 482.17V480.34V480.33Z" fill="#DEEB34"/>
<path d="M1048.16 768.08C1048.16 768.08 1061.91 744.67 1063.97 734.72C1066.04 724.77 1068.1 712.8 1064.72 701.98C1061.34 691.16 1052.28 678.21 1052.28 678.21C1052.28 678.21 1075.23 682.53 1086.88 704.5C1098.52 726.47 1092.33 742.05 1089.32 753.13C1086.32 764.21 1079.74 782.23 1079.74 782.23C1079.74 782.23 1080.49 762.7 1079.74 755.38C1078.99 748.06 1077.67 744.3 1070.91 749.37C1064.15 754.44 1051.2 767.39 1051.2 767.39L1048.15 768.08H1048.16Z" fill="#DEEB34"/>
<path d="M1000.21 471.41C1000.21 471.41 1008.15 471.23 1016.68 478.54C1025.21 485.86 1025.5 495.62 1022.56 510.77C1019.63 525.92 1010.68 560.26 1008.56 576.38C1006.44 592.5 1001.9 612.83 1009.63 634.86C1017.36 656.89 1038.81 670.03 1038.81 670.03C1038.81 670.03 1001.4 651.73 992.04 632.15C982.69 612.57 980.25 596.24 985.47 570.71C990.69 545.19 998.42 516.97 1001.7 500.93C1004.99 484.89 1004.21 477.56 1002.86 475.43C1001.51 473.3 1000.2 471.39 1000.2 471.39L1000.21 471.41Z" fill="#DEEB34"/>
<path d="M1003.79 497.89C1004.96 503.21 1014.23 528.72 1009.61 558.42C1005 588.12 1021.23 519.51 1023.08 507.97C1024.93 496.44 1026.24 488.41 1019.19 481.05C1012.14 473.69 1004.64 471.81 1002.43 471.61L1000.21 471.41C1000.21 471.41 1003.53 475.9 1004.15 482.42C1004.76 488.94 1003.79 497.89 1003.79 497.89V497.89Z" fill="#AAAE11"/>
<path d="M943.41 478.52L958.16 500.89L954.94 505.85L940 480.33L943.41 478.52Z" fill="#AAAE11"/>
<path d="M983.57 442.52L1001.12 471.42C1001.12 471.42 1008.92 473.64 1013.96 476.4C1019 479.16 1017.9 476.82 1016.67 475.45C1015.44 474.08 998.61 446.45 996.02 445.7C993.43 444.95 986.21 443.56 985.78 442.52C985.35 441.48 983.56 442.52 983.56 442.52H983.57Z" fill="#AAAE11"/>
<path d="M987.14 615.44C989.43 621.88 993.41 627.85 999.81 635.72C1006.21 643.59 1015.88 647.36 1015.88 647.36L1025.94 661.42C1025.94 661.42 1006.91 651.69 997.13 638.43C987.35 625.17 987.14 615.44 987.14 615.44V615.44Z" fill="#AAAE11"/>
<path d="M1087.14 753.97C1089.82 743.76 1086.5 723.73 1077.95 711.87C1069.4 700.01 1064.55 696.94 1064.55 696.94L1055.24 678.2C1055.24 678.2 1074.61 684.02 1084.89 701.07C1095.17 718.12 1094.36 731.04 1093.15 737.31C1091.93 743.58 1087.14 753.96 1087.14 753.96V753.97Z" fill="#AAAE11"/>
<path d="M663.11 494.49C663.11 494.49 693.21 493.65 740.1 497.72C786.99 501.79 880.38 524.04 910.43 533.48C910.43 533.48 826.19 514.19 796.33 509.37C766.47 504.54 699.89 496.46 682.16 496.68C664.42 496.9 663.12 494.49 663.12 494.49H663.11Z" fill="#B4BF8F"/>
<path d="M870.81 367.6C870.81 367.6 878.9 401.49 895.58 431.57C912.25 461.65 943.52 499.9 947.27 503.92C951.02 507.94 926.63 481.64 915.5 468.77C904.37 455.9 883.1 416.83 876.66 407.89C870.22 398.94 867.26 384.98 867.26 384.98C867.26 384.98 868.92 367.47 869.24 367.6C869.56 367.73 870.81 367.6 870.81 367.6Z" fill="#B4BF8F"/>
<path d="M850.87 369.64C850.87 369.64 862.73 403.37 880.18 430.63C897.63 457.89 938.14 501.85 938.14 501.85C938.14 501.85 908.67 476.15 876.07 430.63C843.47 385.12 847.76 378.65 847.76 378.65L850.88 369.63L850.87 369.64Z" fill="#B4BF8F"/>
<path d="M934.45 530.61L940.81 506.78C940.81 506.78 949.74 513.68 950.15 517.88C950.56 522.09 950.05 527.02 943.27 529.69C936.49 532.36 934.44 530.61 934.44 530.61H934.45Z" fill="#B4BF8F"/>
<path d="M4.32007 678.12C4.32007 678.12 12.2001 604.24 32.8901 544.94C53.5801 485.64 72.8001 435.61 101.45 389.31C130.1 343.01 180.92 284.36 206.21 262.03C231.5 239.7 240.77 233.22 254.68 233.08C268.58 232.94 271.24 244.93 272.74 262.03C274.25 279.14 283.28 334.28 288.09 356.38C292.9 378.48 299.63 407.57 299.63 407.57C299.63 407.57 315.06 348.37 339.66 299.3C364.26 250.23 388.38 226.79 400.36 222.99C412.34 219.19 420.25 221.01 429.33 226.2C438.41 231.39 448.75 249.11 448.75 249.11C448.75 249.11 464.7 234.94 474.2 225.6C483.7 216.26 502.13 185.22 502.13 185.22C502.13 185.22 458.61 169.33 426.14 132.18C393.67 95.03 380.17 66.95 379.66 58.54L379.15 50.13C379.15 50.13 410.82 78.46 446.68 100.33C482.54 122.2 504.02 125.53 519.05 132.42C534.08 139.31 537.07 137.41 538.53 142.89C539.99 148.37 542.4 152.56 542.4 152.56C542.4 152.56 553.11 145.17 574 145.35C594.89 145.53 597.95 147.95 597.95 147.95C597.95 147.95 580.33 126.46 566.14 91.4C551.95 56.34 548.35 31.77 547.84 25.4C547.33 19.03 546.64 9.45996 546.64 9.45996C546.64 9.45996 571.28 59.04 601.86 90.38C632.44 121.72 642.58 118.49 646.2 127.84C649.81 137.19 645.18 144.7 644.42 149.52C643.66 154.34 644.49 162.29 644.49 162.29C644.49 162.29 643.9 163.1 650.01 169.09C656.12 175.08 673.12 191.87 678.77 203.51C684.42 215.14 683.39 220.74 680.68 224.11C677.97 227.48 677.82 227.29 678.26 229C678.7 230.71 681.45 233.34 681.06 239.26C680.67 245.18 679.25 255.27 679.86 257.33C680.47 259.39 691.4 269.61 703.58 280.13C715.76 290.65 720.64 294.86 720.64 294.86C720.64 294.86 728.68 286.96 738.26 290.28C747.85 293.6 753.23 300.13 751.72 314.62C750.21 329.11 742.5 340.64 742.5 340.64C742.5 340.64 741.54 347.97 738.26 355.37C734.98 362.77 730.49 366.31 730.49 366.31L731.16 353.55C731.16 353.55 726.75 361.63 724.62 366.68C722.49 371.73 711.54 379.98 697.43 380.9C683.32 381.83 680.12 381.21 680.12 381.21L677.59 382.87L677 378.97C677 378.97 660.44 373.73 653.14 360C645.84 346.27 633.44 330.26 621.74 319.58C610.04 308.9 611.63 307.76 598.97 308.28C586.31 308.8 557.97 319.19 543.43 306.73C528.89 294.27 511.52 264.64 511.52 264.64C511.52 264.64 471.7 313.6 460.34 393.35C448.98 473.1 458.06 556.12 486 648.88C513.95 741.64 522.2 757.54 529.53 796.59C536.86 835.64 539.67 862.09 539.67 862.09C539.67 862.09 567.37 824.2 567.87 794.79C568.37 765.38 556.55 731.07 551.51 709.5C546.47 687.94 550.11 672.68 553.51 662.25C556.91 651.82 561.75 646.79 564.26 645C566.77 643.21 571.25 641.5 571.25 641.5L566.59 653.56C566.59 653.56 574.91 655.28 580.39 659.25C585.87 663.22 589.31 671.48 588.24 676.36C587.17 681.24 587.02 687.11 587.02 687.11C587.02 687.11 589.53 679.94 598.49 672.6C607.45 665.25 613.25 662.4 613.25 662.4L574.49 870.52C574.49 870.52 573.8 874.4 577.98 875C582.16 875.6 625.21 881.4 625.21 881.4L702.17 888.05C702.17 888.05 703.17 884.06 704.63 878.27C706.09 872.48 714 856.51 714 856.51L714.95 871.41C714.95 871.41 719.69 872.79 723.82 874.62C727.95 876.45 731.92 878.27 731.92 878.27C731.92 878.27 734.67 867.28 737.58 863.62C740.48 859.95 746.41 853.74 746.41 853.74L742.04 870.04C742.04 870.04 746.47 870.8 751.96 874.32C757.45 877.84 763.06 885.17 763.06 885.17C763.06 885.17 766.98 879.67 769.56 877.53C772.13 875.39 774.54 874.63 774.54 874.63L778.1 862.86L782.69 876.01C782.69 876.01 791.56 877.46 794.77 884.07C797.98 890.68 800.58 897.54 800.58 897.54C800.58 897.54 806.63 897.56 809.72 899.55C812.81 901.54 814.19 907.35 812.5 911.47C810.82 915.6 801.02 930.12 788.88 929.66C776.73 929.2 767.41 922.8 767.41 922.8C767.41 922.8 762.81 933.48 749.21 943.11C735.61 952.74 712.07 960.08 710.39 959.93C708.71 959.78 709.79 990.12 706.8 1005.45C703.81 1020.78 699.71 1036.66 699.71 1036.66H160.76C160.76 1036.66 177.03 995.13 214.36 955.09C251.69 915.05 280.88 884.87 310.37 823.7C339.87 762.54 342.19 707.19 347.71 675.91C353.23 644.63 352.3 575.71 352.3 575.71C352.3 575.71 333.93 565.74 321.48 555.6C309.03 545.46 300.72 542.18 300.72 542.18C300.72 542.18 292.35 554.55 283.61 575.06C274.86 595.57 264.21 617.67 264.21 617.67C264.21 617.67 265.01 597.97 267.91 573.12C270.81 548.27 275.4 520.48 275.4 520.48C275.4 520.48 258 506.46 242.77 520C227.54 533.53 220.54 555.53 214.74 577.04C208.94 598.55 202.68 625.64 202.68 625.64C202.68 625.64 199.27 572.19 196.25 549.74C193.23 527.28 186.99 517.62 176.06 521.46C165.13 525.3 155.03 536.71 143.19 575.08C131.35 613.45 127.48 653.9 126.51 658.73C125.54 663.56 124.09 684.11 124.09 684.11C124.09 684.11 114.07 639.16 111.83 612.57C109.59 585.99 107.17 558.19 104.75 550.22C102.33 542.24 93.4401 532.1 80.3401 540.07C67.2401 548.04 53.7401 565.98 35.8701 601.94C18.0001 637.9 4.30005 678.17 4.30005 678.17L4.32007 678.12Z" fill="#A6CF3D"/>
<path d="M524.59 1033.99C524.59 1033.99 532.39 1015.81 533.63 1010.65C534.88 1005.48 537.02 999.07 537.02 999.07L556.09 982.32L551.1 998.54C551.1 998.54 572.48 987.8 591.73 961.63C610.97 935.46 614.36 925.66 619.35 909.62C624.34 893.58 625.24 881.38 625.24 881.38L700.6 887.89C700.6 887.89 706.99 916.06 708.08 945.17C709.17 974.28 708.29 997.41 704.66 1014.4C701.02 1031.39 699.71 1036.64 699.71 1036.64L524.59 1034V1033.99Z" fill="#DEEB34"/>
<path d="M538.95 859.6C538.95 859.6 560.82 827.85 564.76 798.73C568.7 769.61 563.31 746.97 555.52 726.02C547.73 705.07 547.11 687.06 550.32 674.64C553.53 662.22 558.78 652.69 561.53 648.83C564.28 644.97 571.27 641.47 571.27 641.47C571.27 641.47 568.42 647.29 567.26 649.67C566.1 652.05 566.61 653.53 566.61 653.53C566.61 653.53 575.74 654.15 580.41 659.22C585.08 664.29 584.81 668.84 586.54 672.59C588.26 676.34 587.16 684.72 587.16 684.72C587.16 684.72 589.2 680.14 596.57 674.25C603.94 668.35 613.26 662.38 613.26 662.38L620.19 629.93C620.19 629.93 594.57 575.85 585.59 555.64C576.61 535.43 563.87 497.59 560.64 463.58C557.41 429.57 555.12 414.34 559.07 383.24C563.03 352.15 581.15 316.44 581.15 316.44C581.15 316.44 557.53 317.56 543.44 306.73C529.35 295.9 524.76 288.11 520.06 278.1C515.35 268.09 511.8 262.17 511.8 262.17C511.8 262.17 477.72 311.52 466.27 363.09C454.82 414.66 452.48 484.72 462.47 547.32C472.46 609.92 489.46 660.72 499.74 690.58C510.02 720.44 518.31 743.52 526.32 781.08C534.33 818.64 538.94 859.62 538.94 859.62L538.95 859.6Z" fill="#DEEB34"/>
<path d="M518.45 130.95C518.9 131 535.86 130.11 550.89 134.13C565.92 138.15 576.19 143.8 576.19 143.8C576.19 143.8 565.38 142.65 557.74 145.82C550.1 148.99 541.3 150.37 541.3 150.37C541.3 150.37 542.57 142.31 536.47 139.04C530.37 135.77 518.47 130.95 518.47 130.95H518.45Z" fill="#30923A"/>
<path d="M415.98 235.65C415.98 235.65 424.57 222.64 437.19 210.97C449.81 199.3 468.93 191.23 477.15 188.54C485.38 185.85 494.2 183.16 494.2 183.16L502.87 186.69C502.87 186.69 487.17 203.76 463.71 241.87C440.24 279.98 418.11 312.24 400.45 360.08C382.8 407.93 371.04 454.26 366.35 479.5C361.66 504.74 358.28 535.61 358.28 535.61L345.68 513.16L325.33 474.67C325.33 474.67 329.83 465.52 341.05 455.08C352.27 444.64 363.93 434.85 369.19 419.17C374.45 403.49 382.34 384.77 389.21 366.82C396.08 348.87 399.3 340.14 396.65 336.16C393.99 332.18 385.08 334.83 375.79 341.47C366.5 348.11 356.69 360.08 356.69 360.08C356.69 360.08 366.69 336.54 376.93 324.97C387.17 313.4 395.33 307.33 404.81 302.02C414.29 296.71 424.94 287.42 431.95 276.23C438.95 265.04 454.46 238.25 459.44 232.04C464.42 225.82 466.17 218.87 461.94 217.02C457.7 215.17 443.32 216.63 435.55 221.68C427.77 226.73 416 235.66 416 235.66L415.98 235.65Z" fill="#30923A"/>
<path d="M263.62 775.68C263.87 775.22 272.1 761.44 283.58 754.65C295.05 747.85 304.54 747.73 307.24 755.66C309.94 763.59 306.78 781.89 298.52 805.24C290.26 828.59 276.19 854.73 258.08 879.18C239.97 903.63 228.88 917.89 221.41 924C213.94 930.11 185.47 940.06 165.88 951.69C146.29 963.32 135.27 971.28 129.14 981.39C123.02 991.49 117.81 1008.4 117.81 1008.4C117.81 1008.4 129.1 995.53 140.37 988.18C151.64 980.83 162.03 985.38 166.4 988.13C170.77 990.88 167.88 1002.8 166.3 1011.96C164.72 1021.11 162.22 1026.22 162.22 1026.22C162.22 1026.22 193.09 978.06 210.48 959.3C227.88 940.54 261.39 903.88 284.96 869.46C308.53 835.05 326.6 790.04 335.17 757.59C343.74 725.14 343.04 678.42 348.29 642.23C353.54 606.04 355.12 575.9 355.12 575.9L337.45 569.66C337.45 569.66 336.09 579.41 333.33 613.7C330.57 647.99 327.22 682.92 308.53 707.99C289.84 733.07 285.85 729.72 275.16 747.18C264.47 764.64 263.61 775.67 263.61 775.67L263.62 775.68Z" fill="#30923A"/>
<path d="M563.23 227.79C563.23 227.79 570.52 223.95 576.28 223.8C582.05 223.65 592.19 224.66 601.99 230.3C611.8 235.94 621.81 233.84 623.64 240.66C625.46 247.48 629.15 266.45 629.15 266.45C629.15 266.45 625.63 260.5 621.75 260.29C617.87 260.08 599.05 263.31 583.57 251.98C568.09 240.65 563.23 227.79 563.23 227.79Z" fill="white"/>
<path d="M665.63 246.95C665.63 246.95 667.96 234.5 668.4 233.47C668.84 232.44 676.88 227.93 677.2 227.72C677.52 227.51 678.16 230.62 677.2 235.38C676.24 240.14 670.32 251.39 670.32 251.39L665.63 246.96V246.95Z" fill="#D4D4D4"/>
<path d="M493.29 377.45C489.48 400.94 485.16 467.06 495.97 525.31C506.77 583.56 524.59 622.86 524.59 622.86C524.59 622.86 512.76 571.59 507.21 542.39C501.65 513.19 498.08 483.09 495.97 452.58C493.87 422.06 493.29 377.45 493.29 377.45V377.45Z" fill="white"/>
<path d="M716.24 334.84C716.24 334.84 719.05 338.57 726.74 339.8C734.43 341.03 740.89 334.23 743.34 328.44C745.79 322.65 751.35 309.34 747.4 298.62C743.45 287.9 753.9 304.15 751.74 314.6C749.58 325.05 741.88 342.76 741.4 343.96C740.92 345.16 736.96 349.78 732.58 348.88C728.2 347.98 721.84 345.94 719.44 343C717.04 340.06 716.24 334.85 716.24 334.85V334.84Z" fill="#30923A"/>
<path d="M718.15 356.64C719.12 353.88 720.32 352.38 722.26 353.13C724.2 353.88 722.86 358.66 721.21 362.1C719.57 365.54 718.37 368.06 714.86 370.01C711.35 371.96 702.26 373.18 702.26 373.18C702.26 373.18 694.77 375.83 685.8 375.09L680.72 379.8C680.72 379.8 699.3 381.79 710.61 377.04C721.92 372.29 724.5 364.63 727.42 360.75C730.34 356.87 731.86 351.38 731.91 351.18C731.96 350.98 733.7 348.95 730.27 347.68C726.83 346.41 723.18 343.07 720.9 345.75C718.62 348.43 718.17 356.65 718.17 356.65L718.15 356.64Z" fill="#30923A"/>
<path d="M701.96 370.19C699.28 369.86 697.01 368.78 697.06 367.31C697.11 365.84 698.43 364 703.21 364.79C707.99 365.58 701 360.81 698.01 360.64C695.02 360.47 691.34 361.48 691.23 361.69C691.12 361.9 688.5 368.72 688.5 368.72C688.5 368.72 690.02 367.31 693.33 368.72C696.64 370.14 700.95 371.14 700.95 371.14L701.96 370.18V370.19Z" fill="#30923A"/>
<path d="M683.61 380.22C685.08 379.45 690.97 374.83 693.33 368.73C695.69 362.63 689.7 363.74 689.7 363.74C689.7 363.74 685.5 373.99 683.61 376.03C681.72 378.08 680.19 379.71 680.19 379.71L683.61 380.21V380.22Z" fill="#30923A"/>
<path d="M716.24 334.84C716.24 334.84 711.04 345.78 696.66 350.97C682.28 356.15 670.04 349.6 664.87 342.91C659.7 336.22 662.27 324.59 662.27 324.59L653.44 327.75C653.44 327.75 651.36 339.85 657.42 346.92C663.47 354 680.07 359.88 679.87 360.99C679.67 362.1 685.67 363.77 689.22 362.48C692.77 361.19 703.28 358.24 707.98 352.97C712.68 347.7 714.09 343.36 715.11 340.74C716.13 338.12 716.24 334.84 716.24 334.84V334.84Z" fill="#30923A"/>
<path d="M674.03 312.34C672.33 313.33 670.94 314.83 671.04 317.67C671.14 320.51 673.88 322.49 677.49 322.04C681.1 321.59 684.25 321.71 686.57 322.04C688.89 322.37 691.48 322.64 691.48 322.64C691.48 322.64 686.37 319.11 681.26 316.59C676.15 314.06 674.03 312.34 674.03 312.34Z" fill="#30923A"/>
<path d="M731.18 319.57C731.18 319.57 731.51 313.99 732.56 310.43C733.61 306.87 735.72 303.72 739.32 302.87C742.93 302.02 745.39 304.77 745.34 307.33C745.28 309.89 741.58 308.09 738.82 310.44C736.07 312.79 731.18 319.58 731.18 319.58V319.57Z" fill="#30923A"/>
<path d="M668.37 327.92C668.37 327.92 660.76 326.67 657.17 322.64C653.58 318.61 654.13 309.28 659.4 303.03C664.67 296.77 659.4 303.89 659.31 310.43C659.22 316.97 659.45 317.14 661.27 320.59C663.09 324.04 668.37 327.92 668.37 327.92Z" fill="#30923A"/>
<path d="M598.58 287.33C606.21 287.09 612.73 286.49 619.68 287.77C626.63 289.06 655.53 313.65 655.53 313.65C655.53 313.65 657.19 320.7 659.73 322.64C662.27 324.58 661.27 326.11 661.27 326.11L657.5 330.74L651.58 327.92C651.58 327.92 621.22 296.26 614.36 292.49C607.5 288.72 599.26 288.2 599.26 288.2L598.58 287.33V287.33Z" fill="#30923A"/>
<path d="M631.04 271.34C631.04 271.34 601.67 269.76 582.99 253.84C564.31 237.92 578.12 248.31 578.12 248.31C578.12 248.31 590.32 260.12 603.62 260.3C616.92 260.48 623.77 259.82 626.09 261.26C628.41 262.7 631.04 271.34 631.04 271.34V271.34Z" fill="#30923A"/>
<path d="M640.16 251.83C638.76 252.41 635.59 253.43 632.89 258.15C630.19 262.88 631.04 271.33 631.04 271.33C631.04 271.33 630.05 262.93 627.74 254.65C625.43 246.36 621.29 239.19 621.29 239.19C621.29 239.19 625.44 238.54 630.03 235.38C634.61 232.22 637 226 637 226C637 226 637.73 230.81 635.85 235.89C633.96 240.98 634.55 244.71 635.4 247.16C636.54 250.47 640.17 251.83 640.17 251.83H640.16Z" fill="#30923A"/>
<path d="M543.45 219.94C543.45 219.94 549.96 212.62 566.15 211.5C582.34 210.37 597.23 217.45 609.98 222.12C622.72 226.78 627.34 227.65 630.73 227.46C634.12 227.27 636.3 223.94 636.2 222C636.2 222 637.81 235.38 625.15 235.89C612.49 236.4 598.84 228.29 586.47 222.76C574.1 217.23 568.61 214.54 559.99 215.44C551.37 216.34 543.46 219.95 543.46 219.95L543.45 219.94Z" fill="#30923A"/>
<path d="M543.45 219.94C543.45 219.94 556.42 228.11 567.26 239.55C578.1 250.99 562.18 231.16 562.49 229.43C562.8 227.7 566.96 227.78 572.75 225.12C578.54 222.46 579.33 218.81 570.55 217.06C561.77 215.3 553.27 216.01 550.02 217.06C546.77 218.11 543.44 219.95 543.44 219.95L543.45 219.94Z" fill="#30923A"/>
<path d="M634.66 157.89C634.66 157.89 645.57 166.32 659.68 187.01C673.79 207.7 675.39 224.62 675.56 226.6C675.73 228.58 668.4 231.13 668.4 231.13C668.4 231.13 664.24 206.5 655.11 190.17C645.99 173.84 634.66 157.89 634.66 157.89V157.89Z" fill="#30923A"/>
<path d="M669.06 192.25C673.17 198.25 676.32 209.98 674.27 219.02C672.22 228.07 674.73 228.3 677.71 226.2C680.69 224.1 683.35 225.48 681.73 215.95C680.11 206.42 678.91 204.06 675.57 198.71C672.23 193.35 669.06 192.25 669.06 192.25V192.25Z" fill="#30923A"/>
<path d="M670.55 251.91C670.55 251.91 673.85 248.32 675.57 241.62C677.29 234.91 678.43 229.43 678.43 229.43C678.43 229.43 680.82 237.57 680.57 244.97C680.32 252.37 679.26 260.62 679.26 260.62L670.55 251.91Z" fill="#30923A"/>
<path d="M545.62 155.85C549.04 153.37 564.17 145.31 581.54 146.76C598.9 148.21 580.18 144.29 569.77 144.75C559.36 145.21 546.26 147.62 544.28 148.52C542.31 149.42 541.59 151 541.59 151L545.62 155.84V155.85Z" fill="#30923A"/>
<path d="M566.15 155.12C566.15 155.12 572.56 154.2 583.46 155.12C594.36 156.04 607.24 160.83 607.24 160.83C607.24 160.83 607.32 168.57 600.16 165.54C592.99 162.52 574.7 156.65 574.39 156.57C574.08 156.49 566.15 155.11 566.15 155.11V155.12Z" fill="#30923A"/>
<path d="M649.05 248.98C653.78 247.91 659.05 245.71 661.25 240.66C663.45 235.61 660.37 217.02 654.43 203.04C648.49 189.07 639.23 174.76 628.24 161.45C617.24 148.14 596.16 127.86 596.16 127.86C596.16 127.86 618.5 141.02 629.93 154.36C641.36 167.71 654.12 188 661.05 203.44C667.97 218.88 669.57 231.29 668.42 237.58C667.27 243.87 664.81 247.43 659.95 248.3C655.08 249.16 649.07 248.97 649.07 248.97L649.05 248.98Z" fill="#30923A"/>
<path d="M592.3 141.7L600.86 142.89L603.91 150.29L596.15 148.57L592.3 141.7Z" fill="#30923A"/>
<path d="M623.24 145.82C627.21 146.52 633.77 148.47 636.25 145.82C638.73 143.17 639.75 134.39 638.73 130.9C637.71 127.42 622.4 118.23 608.55 104.23C594.7 90.23 568.33 63.95 548.97 18.15C548.97 18.15 568.99 51.65 587.95 74.49C606.91 97.33 633.39 116.23 641.03 121.7C648.67 127.17 648.6 133.87 647.15 139.37C645.7 144.86 644.69 162.99 644.69 162.99C644.69 162.99 637.59 158.14 632.19 155.12C626.79 152.1 623.41 148.98 623.41 148.98L623.22 145.81L623.24 145.82Z" fill="#30923A"/>
<path d="M396.57 90.5701C405.92 105.09 420.54 123.34 448.75 144.72C476.96 166.1 522.9 190.17 522.9 190.17C522.9 190.17 472.44 175.53 450.43 155.12C428.42 134.71 412.4 121.48 407.04 111.32C401.68 101.16 396.58 90.5701 396.58 90.5701H396.57Z" fill="#30923A"/>
<path d="M522.9 190.17C522.9 190.17 512.05 188.74 503.58 198.15C495.11 207.56 453.48 260.57 431.96 304.98C410.43 349.39 395.98 384.91 380.19 448.84C364.4 512.78 365.09 542.39 365.09 542.39L358.78 533.56C358.78 533.56 364.01 483.93 372.01 453.5C380.01 423.07 396.47 366.72 406.69 344.26C416.91 321.8 435.01 281.79 451.09 259.06C467.17 236.33 502.14 185.21 502.14 185.21C502.14 185.21 510.4 186.81 511.05 186.68C511.7 186.55 522.9 190.16 522.9 190.16V190.17Z" fill="#30923A"/>
<path d="M275.59 527.71C277.42 539.98 281.94 549.15 284.34 553.38C286.74 557.61 288.96 561.56 288.96 561.56L299.83 541.4L275.4 520.44L275.6 527.71H275.59Z" fill="#30923A"/>
<path d="M264.94 610.23C264.94 610.23 271.64 588.55 277.84 573.09C284.05 557.62 291.24 547.04 291.24 547.04L295.19 551.27L264.94 610.23V610.23Z" fill="#30923A"/>
<path d="M316.48 391.19C316.48 391.19 338.04 347.92 355.31 320.59C372.58 293.26 386.76 287.1 396.01 285.66C405.26 284.22 412.57 291.91 414.11 294.5C414.11 294.5 421.72 291.1 427.97 280.93C434.21 270.76 443.69 271.06 427.97 263.46C412.25 255.86 385.51 252.87 374.47 268.01C363.44 283.14 346.44 311.03 339.66 330.89C332.88 350.75 316.49 391.2 316.49 391.2L316.48 391.19Z" fill="#30923A"/>
<path d="M377.53 260.75C383.15 254.76 393.48 246.34 406.1 242.77C418.73 239.2 427.18 245.11 431.95 249.1C436.71 253.1 441.34 260.74 441.34 260.74L435.65 266.2C435.65 266.2 423.45 251.08 411.08 250.17C398.71 249.26 377.53 260.74 377.53 260.74V260.75Z" fill="#30923A"/>
<path d="M410.49 222.54C414.49 222.67 423.7 224.61 430.71 233.05C437.72 241.48 443.41 255.13 443.41 255.13L448.83 248.35C448.83 248.35 440.83 232.06 429.66 226.39C418.49 220.72 411.09 220.98 411.09 220.98L410.49 222.54Z" fill="#30923A"/>
<path d="M332.23 461.78C334.44 449.97 346.52 404.35 351.53 387.39C356.54 370.43 362.14 368.96 363.9 369.25C365.66 369.54 369.04 380.32 369.04 397.19C369.04 414.06 369.21 425.23 369.21 425.23C369.21 425.23 352.12 452.59 344.46 459.83C336.8 467.06 332.23 461.79 332.23 461.79V461.78Z" fill="#30923A"/>
<path d="M307.03 426.42L314.44 401.78C314.44 401.78 314.22 444.47 314.44 450.2C314.66 455.93 307.03 426.43 307.03 426.43V426.42Z" fill="#30923A"/>
<path d="M65.6899 481.9C65.6899 481.9 87.3999 448.99 110.51 413.17C133.63 377.35 160.55 343.32 178.59 325.79C196.63 308.26 211.1 298.61 225.84 296.58C240.57 294.55 248.96 305.6 253.53 317.6C258.1 329.6 263.18 352.79 273.6 385.52C284.01 418.25 291.28 435.78 300.73 457.88C310.18 479.98 338.88 523.04 338.88 523.04C338.88 523.04 294.14 440.04 283 391.39C271.86 342.74 264.47 306.4 258.11 292.72C251.75 279.04 243.87 272.14 226.1 274.73C208.33 277.32 184.54 302.72 160.31 329.97C136.07 357.21 113.55 394.66 102.65 411.14C91.75 427.62 65.7 481.9 65.7 481.9H65.6899Z" fill="#30923A"/>
<path d="M111.84 514.59C111.84 514.59 127.02 485.18 133.32 472.91C139.62 460.64 145.09 449.69 147.08 449.69C149.07 449.69 149.4 454.17 147.08 462.79C144.76 471.41 139.11 500.39 139.11 500.39C139.11 500.39 154.33 438.99 161.87 424.49C169.42 409.99 170.47 402.98 167.81 404.26C165.14 405.54 164.33 406.56 156.7 417.53C149.07 428.5 131.46 467.56 127.26 476.93C123.06 486.31 111.83 514.59 111.83 514.59H111.84Z" fill="#30923A"/>
<path d="M194.05 474.61C194.05 474.61 203.62 437.83 207.45 424.34C211.27 410.85 214.49 411.57 216.07 413.11C217.65 414.65 218.01 422.74 219.08 431.89C220.15 441.04 224.69 474.6 224.69 474.6C224.69 474.6 221.37 409.63 222.55 394.66C223.73 379.69 223.16 369.01 220.84 371.55C218.53 374.09 214.8 382.84 211.91 392.16C209.02 401.48 202.59 429.58 200.62 439.3C198.65 449.02 194.05 474.6 194.05 474.6V474.61Z" fill="#30923A"/>
<path d="M6.72998 670.28C6.72998 670.28 13.42 640.41 28.44 600.71C43.47 561.01 59.73 541.39 73.12 530.82C86.51 520.25 104.13 523.74 108.83 533.86C113.53 543.98 111.13 572.47 114.27 611.64C117.42 650.81 108.36 580.4 107.42 565.83C106.48 551.26 102.72 539.99 92.38 537.4C82.04 534.82 71.47 545.86 59.02 564.42C46.57 582.98 34.5799 602.01 28.4799 617.99C22.3699 633.97 6.72998 670.28 6.72998 670.28V670.28Z" fill="#30923A"/>
<path d="M123.01 669.16C123.01 669.16 121.53 651.83 127.4 611.65C133.27 571.47 141.07 551.62 150.9 535.61C160.2 520.45 167.21 513.87 176.76 512.32C188.29 510.45 194.7 521 197.01 538.23C199.33 555.46 193.7 530.74 188.16 524.44C182.62 518.14 169.93 519.78 164.76 528.48C159.59 537.17 144.32 564.19 138.92 590.04C133.52 615.88 127.75 648.21 126.53 658.69C125.31 669.18 123.02 669.16 123.02 669.16H123.01Z" fill="#30923A"/>
<path d="M382.09 584.38C361.19 576.12 336.13 562.47 318.98 545.63C301.83 528.8 295.23 521.27 281.57 512.1C267.91 502.93 253.79 502.29 240.69 510.43C227.59 518.57 221.17 530.36 214.76 555.76C208.34 581.16 204.22 611.72 204.22 611.72C204.22 611.72 217.34 558.79 228.38 539.32C239.42 519.85 247.51 513.75 260.38 514.23C273.25 514.72 285.92 527.55 302.11 542.76C318.3 557.97 334.04 567.26 349.55 574.13C365.05 580.99 382.1 584.38 382.1 584.38H382.09Z" fill="#30923A"/>
<path d="M528.13 802.64C529.43 815.43 530.94 842.38 530.19 853.35C529.44 864.32 528.13 873.18 528.13 873.18L540.95 860.26C540.95 860.26 540.55 849.22 536.94 833.52C533.33 817.82 528.13 802.64 528.13 802.64V802.64Z" fill="#30923A"/>
<path d="M582.76 875.63C582.76 875.63 581.18 883.85 581.04 885.11C580.9 886.37 582.16 886.92 585.5 887.48C588.85 888.04 622.86 892.5 622.86 892.5L625.29 878.99L582.76 875.63Z" fill="#30923A"/>
<path d="M609.71 884.06C609.71 884.06 606.22 908.52 593.21 937.22C580.2 965.92 554.88 993.52 554.88 993.52C554.88 993.52 576.38 981.66 590.89 962.74C605.4 943.82 615.29 924.04 618.81 911.33C622.33 898.62 623.55 888.65 623.55 888.65L609.71 884.05V884.06Z" fill="#30923A"/>
<path d="M567.85 957.74C567.85 957.74 544.24 986.93 497.29 1002.11C450.35 1017.3 411.12 1024.18 387.08 1023.43C363.04 1022.68 430.68 1033.59 450.08 1031.76C469.48 1029.93 524.58 1036.63 524.58 1036.63L538.18 997.19C538.18 997.19 549.93 994.08 554.87 986.24C559.81 978.4 561.64 972.91 563.67 969.18C565.7 965.45 567.84 957.75 567.84 957.75L567.85 957.74Z" fill="#30923A"/>
<path d="M764.01 925.88C755.35 934.14 741.43 940.15 729.8 941.83C718.17 943.52 708.8 941.83 708.8 941.83L701.56 892.49C701.56 892.49 704.91 909.59 719.51 915.91C734.11 922.23 738.25 920.93 738.25 920.93C738.25 920.93 742.39 925.5 750.45 925.71C758.51 925.93 764.01 925.88 764.01 925.88V925.88Z" fill="#30923A"/>
<path d="M735.49 918.99C727.89 913.86 723.72 902.71 723.72 890.7C723.72 878.69 725.11 878.99 722.83 877.31C720.55 875.62 713.37 873.47 713.37 873.47C713.37 873.47 718.89 873.06 724.43 874.84C729.97 876.62 731.66 880.19 731.66 880.19C731.66 880.19 729.08 892.39 731.16 902.11C733.24 911.83 735.49 918.99 735.49 918.99V918.99Z" fill="#30923A"/>
<path d="M707.37 895.76C708.59 899.36 711.22 904.74 719.21 910.01C727.2 915.27 735.49 914.88 735.49 914.88L738.26 920.93C738.26 920.93 726.33 921.11 717.95 914.19C709.57 907.27 707.38 895.75 707.38 895.75L707.37 895.76Z" fill="#30923A"/>
<path d="M736.3 913.54C738.16 916.69 743.16 919.67 749.22 920.93C755.28 922.19 761.74 918.58 763.09 916.43C764.44 914.29 766.76 921.59 766.76 921.59C766.76 921.59 760.19 928.12 749.22 924.52C738.25 920.92 735.49 914.87 735.49 914.87L736.31 913.54H736.3Z" fill="#30923A"/>
<path d="M754.86 878.56C754.86 878.56 751.34 884.85 752.94 898.8C754.79 914.98 761.26 922.5 762.64 924.2C764.02 925.9 765.71 920.54 765.71 920.54C765.71 920.54 758.3 902.41 760.93 895.1C763.56 887.79 764.46 884.62 763.09 884.04C761.72 883.46 754.86 878.57 754.86 878.57V878.56Z" fill="#30923A"/>
<path d="M786.41 879.99C791.77 884.16 793.64 892.93 795.12 902.19C796.6 911.45 794.53 922.6 789.9 924.52C785.27 926.44 772.35 926.44 766.76 916.12C761.17 905.8 766.56 923.12 775.81 927.67C785.06 932.22 795.38 929.71 801.76 924.77C808.14 919.83 813.06 916.87 812.08 909.78C811.09 902.68 808.43 899.63 805.77 899.23C803.11 898.84 800.15 898.44 800.15 898.44C800.15 898.44 799.95 893.4 795.12 886.9C790.29 880.4 786.41 879.98 786.41 879.98V879.99Z" fill="#30923A"/>
<path d="M522.9 233.47C522.9 233.47 523.44 241.77 525.72 249.33C528 256.89 533.86 264.69 533.86 264.69H547.69C547.69 264.69 538.95 257.68 533.28 250.04C527.61 242.4 522.9 233.48 522.9 233.48V233.47Z" fill="#30923A"/>
<path d="M527.4 261.91C527.4 261.91 531.32 273.73 545.2 284.29C559.08 294.85 576.23 298.17 586.46 299.04C596.69 299.91 607.24 297.45 616.07 304.54C624.89 311.63 633.99 320.67 640.16 327.03C646.33 333.39 654.89 345.36 654.89 345.36L651.59 327.93C651.59 327.93 625.2 302.49 616.63 295.11C608.06 287.73 594.61 290.68 587.22 289.94C579.83 289.2 560.62 287.28 551.75 281.66C542.88 276.04 533.42 268.06 533.42 268.06L527.41 261.92L527.4 261.91Z" fill="#30923A"/>
<path d="M644.36 344.13C648.99 350.77 655.83 359.79 662.72 365.2C669.61 370.61 679.08 373.57 679.08 373.57L678.4 379.47C678.4 379.47 668.7 376.46 661.82 370.39C654.94 364.31 648.64 352.23 648.64 352.23L644.36 344.13V344.13Z" fill="#30923A"/>
<path d="M650.79 339.82C651.95 342.54 652.49 347.18 659.18 355.23C665.87 363.28 678.41 366.67 678.41 366.67L679.88 360.98C679.88 360.98 665.32 358.69 660 350.89C654.68 343.09 650.79 339.82 650.79 339.82V339.82Z" fill="#30923A"/>
<path d="M627.36 321.65C622.51 316.16 614.77 308.16 614.22 306.71C613.67 305.26 615.62 303.77 621.63 309.22C627.63 314.67 621.49 304.5 610.48 299.32C599.47 294.14 580.16 298.28 580.16 298.28C580.16 298.28 586.69 298.99 587.13 301.85C587.57 304.71 572.92 305.83 560.3 304.79C547.68 303.74 538.79 295.9 530.03 284.52C521.27 273.14 504.5 248.3 504.5 248.3C504.5 248.3 520.25 278.12 527.41 290.96C534.57 303.8 548 313.72 563.24 312.81C578.48 311.9 595.02 307.89 602.01 308.17C609 308.45 610.96 309.44 616.45 313.65C621.94 317.86 627.36 321.65 627.36 321.65V321.65Z" fill="#30923A"/>
<path d="M551.95 187.15C551.95 187.15 550.33 165.92 549.74 160.48C549.15 155.04 552.86 151.49 557.97 149.97C563.08 148.45 554.17 149.42 548.97 151.52C543.77 153.62 541.73 152.25 543.54 156.79C545.35 161.33 551.95 187.15 551.95 187.15V187.15Z" fill="#30923A"/>
<path d="M504.49 295.48C507.08 291.49 512.44 290.76 516.95 296.49C521.46 302.22 530.95 311.48 542.35 317.57C553.75 323.66 570.58 324.8 575.74 324.19C575.74 324.19 581.29 316.56 578.17 316.45C575.05 316.34 547.62 313.83 537.17 303.14C526.72 292.45 519.29 280.08 517.11 273.57C514.93 267.05 510.75 263.75 510.75 263.75C501.82 273.19 493.3 289.62 493.3 289.62L504.5 295.49L504.49 295.48Z" fill="#AAAE11"/>
<path d="M504.49 295.48C499.84 302.19 484.54 326.83 474.6 369.22C464.66 411.61 462.21 451.03 465.69 503.92C469.17 556.81 479.28 593.49 490.85 632.6C502.42 671.71 513.75 699.54 524.59 737.04C535.43 774.54 544.3 816.94 546.7 846.92L539.2 857.09C539.2 857.09 532.58 793.72 518.67 749.44C504.76 705.16 486.18 653.43 474.83 608.21C463.49 562.99 456.65 521.97 456.16 496.48C455.67 470.99 452.7 425.11 458.53 398.13C464.36 371.15 477.85 324.75 484.79 310.43C491.73 296.11 500.83 283.27 500.83 283.27L504.48 295.48H504.49Z" fill="#AAAE11"/>
<path d="M565.74 323.95C568.64 324.19 568.77 327.85 567.63 330.74C566.48 333.62 554.51 358.42 551.39 410.82C548.28 463.22 559.08 508.09 568.8 532.38C578.51 556.66 590.99 583.66 601.24 605.61C611.49 627.56 616.71 641.47 616.71 641.47L620.21 629.91C620.21 629.91 591.4 570.9 582.16 548.6C572.92 526.3 563.27 493.75 560.06 465.82C556.85 437.89 556.52 404.54 559.09 384.05C561.66 363.56 570.69 337.28 573.21 330.73C575.74 324.18 581.56 315.72 581.56 315.72L565.75 323.94L565.74 323.95Z" fill="#AAAE11"/>
<path d="M482.56 341.48C481.36 345.06 483.72 344.39 489.7 342.3C495.68 340.21 507.93 336.42 521.38 335.82C534.83 335.22 544.59 336.42 551.27 339.11C557.95 341.8 560.37 344.69 561.11 344.93C561.85 345.17 562.37 344.85 562.83 343.46C563.29 342.06 565.01 342.57 563.75 346.23C562.49 349.89 561.23 352.26 561.23 352.26C561.23 352.26 557.15 343.47 539.11 341.64C521.07 339.8 510.95 341.59 497.3 346.29C483.65 350.99 473.18 357.83 471.23 355.77C469.27 353.72 473.63 342.77 473.63 342.77L482.56 341.5V341.48Z" fill="#AAAE11"/>
<path d="M465.44 427.89C465.27 430.17 467.25 431.89 471.85 429.59C476.45 427.29 484.04 423.14 501.3 419.18C518.55 415.22 527.07 415.33 535.12 416.25C543.17 417.17 546.87 418.77 548.25 419.18C549.63 419.59 550.73 419.16 551.08 417.12C551.43 415.08 552.57 415.84 552.85 420.25C553.13 424.66 553.13 426.04 553.13 426.04C553.13 426.04 537.49 418.31 522.89 420.2C508.29 422.1 492.4 425.71 479.95 431.5C467.5 437.29 460.16 444.55 458.86 441.96C457.56 439.37 465.44 427.89 465.44 427.89Z" fill="#AAAE11"/>
<path d="M467.56 525.59C467.87 528.42 469.77 530.74 473.07 529.24C476.37 527.74 490.04 520.72 509.33 517.63C528.62 514.54 543.98 513.88 552.69 515.19C561.4 516.5 562.93 515.49 562.09 512.49C561.26 509.49 566.46 514.12 567.58 518.26C568.7 522.4 568.42 523.04 568.42 523.04C568.42 523.04 547.06 515.82 520.47 521.09C493.88 526.36 482.46 531.62 475.8 535.04C469.13 538.47 465.03 542.91 464.65 540.57C464.28 538.23 467.56 525.58 467.56 525.58V525.59Z" fill="#AAAE11"/>
<path d="M494.03 643.12C495.13 646.63 498.12 648.35 503.78 645.03C509.44 641.72 520.66 634.67 548.43 627.94C576.2 621.21 594.68 620.57 600.34 619.93C606 619.29 607.23 618.75 606.16 616.38C605.09 614.01 608.88 617.58 610.59 620.46C612.3 623.34 610.59 624.09 610.59 624.09C610.59 624.09 576.48 623.59 548.85 632.6C521.22 641.61 503.3 653.15 499.74 655.6C496.19 658.06 492.02 662.08 491.92 659.21C491.81 656.34 494.03 643.11 494.03 643.11V643.12Z" fill="#AAAE11"/>
<path d="M535.37 778.84C536.11 782.15 537.78 785.68 543.01 784.54C548.24 783.4 565.29 778.84 565.29 778.84V784.76L532.14 793.08L535.37 778.84Z" fill="#AAAE11"/>
<path d="M607.45 942.59C608.4 943.02 628.41 937.5 655.09 933.68C681.77 929.87 706.13 930.06 706.13 930.06L707.6 936.34C707.6 936.34 682.95 935.91 646.92 942.62C610.89 949.32 597.1 954.2 597.1 954.2L607.44 942.6L607.45 942.59Z" fill="#AAAE11"/>
<path d="M645.63 883.13C645.63 883.13 641.5 918.53 616.56 959C591.62 999.46 548.32 1027.1 548.32 1027.1L526.23 1031.87L536.75 1000.55L554.88 986.23L550.49 999.24C550.49 999.24 581.75 976.49 595.99 956.69C610.23 936.89 620.49 911.84 623.54 898.91C626.59 885.98 625.28 878.98 625.28 878.98L645.62 883.13H645.63Z" fill="#AAAE11"/>
<path d="M632.68 893.94C633.29 893.94 702.89 901.54 702.89 901.54L699.73 886.57L633.51 880.67L632.68 893.95V893.94Z" fill="#AAAE11"/>
<path d="M691.94 896.22C691.94 896.22 699.61 922.27 700.25 955.43C700.89 988.59 694.17 1036.54 694.17 1036.54L701.57 1029.14C701.57 1029.14 708.52 991.24 707.37 957.73C706.22 924.22 701.57 892.49 701.57 892.49L691.95 896.21L691.94 896.22Z" fill="#AAAE11"/>
<path d="M591.78 990.55C585.5 996.88 587.84 1006.88 609.74 1005.51C631.64 1004.14 675.17 996.31 685.84 993.52C696.52 990.73 699.58 986.59 699.92 975.38C700.26 964.17 702.78 976.49 701.57 999.22C700.35 1021.94 694.17 1036.54 694.17 1036.54L524.59 1036.62L543.76 1013.99L591.78 990.54V990.55Z" fill="#AAAE11"/>
<path d="M597.34 678.8C600.23 675.95 601.21 683.05 598.56 707.26C595.91 731.47 588.26 795.38 588.26 795.38L613.27 662.37C613.27 662.37 599.21 670.44 598.05 673.08C596.89 675.72 597.34 678.81 597.34 678.81V678.8Z" fill="#30923A"/>
<path d="M564.41 654.58C563.91 657.61 564.03 661.02 566.69 661.66C569.35 662.3 574.54 664.95 577.08 673.69C579.61 682.43 579.36 698.64 578.85 701.56C578.34 704.48 587.62 690.27 587.36 682.32C587.1 674.37 589.44 668.63 582.78 662.37C576.12 656.11 569.56 655.64 568.09 654.58C566.62 653.52 565.3 654.54 566.66 651.34C568.02 648.13 568.94 646.25 568.94 646.25L564.41 654.58Z" fill="#30923A"/>
<path d="M363.28 579.33C363.28 579.33 359.9 631.33 355.28 692.16C350.66 752.99 328.13 824.45 295.76 873.44C263.39 922.43 237.94 946.48 209.97 984.01C182 1021.54 177.04 1040.66 177.04 1040.66L157.75 1036.63C157.75 1036.63 190.49 984.8 219.53 951.48C248.56 918.16 287.96 872.13 303.67 843.1C319.38 814.07 336.37 759.54 342.39 718.83C348.41 678.11 349.98 634.21 350.72 611.73C351.46 589.26 353.69 575.88 353.69 575.88L363.28 579.34V579.33Z" fill="#30923A"/>
<path d="M668.37 327.92C668.37 327.92 663.5 322.63 664.18 316.23C664.86 309.84 668.35 305.82 675.45 303.63C682.54 301.44 690.49 301.59 700.2 308.11C700.2 308.11 696.87 300.6 683.24 298.05C669.61 295.5 661.93 301.17 660.05 305.86C658.17 310.54 657.87 311.64 658.87 316.24C659.87 320.84 660.6 322.03 661.73 323.13C662.86 324.23 668.37 327.92 668.37 327.92V327.92Z" fill="#E4FFA4"/>
<path d="M743.72 294.21C740.61 290.71 734.24 290.42 729.09 293.42C723.24 296.83 719.52 304.3 718.33 306.99C718.33 306.99 724.11 301.75 728.03 298.38C731.95 295.01 733.8 294.15 736.61 293.49C740.81 292.5 743.71 294.21 743.71 294.21H743.72Z" fill="#E4FFA4"/>
<path d="M672.18 335.03C674.81 337.7 680.02 340.02 685.83 339.82C691.64 339.62 697.45 336.1 697.45 336.1C697.45 336.1 692.08 343.76 685.51 343.76C675.03 343.76 672.18 335.03 672.18 335.03Z" fill="#E4FFA4"/>
<path d="M522.9 233.47C522.9 233.47 528.73 241.19 540.99 250C553.25 258.81 575.53 265.91 591.15 268.36C591.15 268.36 580.34 271.89 562.86 271.34C545.38 270.79 536.1 267.55 536.1 267.55C536.1 267.55 543.29 266.45 546.1 265.73C548.91 265.01 546.62 261.83 543.46 260.29C540.3 258.75 532.15 249.88 529.25 245.62C526.35 241.36 522.91 233.47 522.91 233.47H522.9Z" fill="#E4FFA4"/>
<path d="M650.94 276.3C650.94 276.3 655.95 286.67 658.3 292.23C660.65 297.79 660.78 300.46 659.4 303.03C658.02 305.6 665.29 301.09 665.75 298.34C666.21 295.59 656.82 284.31 656.09 283.67C655.36 283.03 650.94 276.3 650.94 276.3V276.3Z" fill="#E4FFA4"/>
<path d="M608.56 203.46C600.04 198.45 585.83 196.11 574.85 197.11C574.85 197.11 577.71 195.18 582.01 193.72C586.31 192.26 587.48 188.69 586.17 185.91C584.86 183.14 579.6 176.57 573.62 172.12C573.62 172.12 582.09 176.39 587.26 180.58C597.95 189.24 608.56 203.45 608.56 203.45V203.46Z" fill="#E4FFA4"/>
<path d="M381.62 55.95C381.62 55.95 395.62 73.93 428.75 98.1C461.88 122.27 487.95 133.73 507.21 139.15C526.47 144.57 535 143.23 538.24 151.44C541.48 159.66 542.83 147.48 531.89 140.38C520.96 133.28 469.58 113.73 448.75 102.9C427.92 92.07 398.96 69.56 391.87 63.1C384.79 56.64 381.62 55.96 381.62 55.96V55.95Z" fill="#E4FFA4"/>
<path d="M403.21 436.53C405.98 415.71 415.42 376.47 427.56 350.24C439.69 324.01 451.41 311.46 451.41 311.46C451.41 311.46 435.35 341.91 424.28 377.45C413.2 412.99 404.12 457.7 402.54 478.6C402.54 478.6 400.96 453.49 403.22 436.52L403.21 436.53Z" fill="#E4FFA4"/>
<path d="M380.03 704.74C380.03 704.74 398.01 718.42 407.49 734.48C416.97 750.54 415.2 753.12 413.7 754.48C412.2 755.84 410.16 753.39 402.81 753.66C395.46 753.93 391.52 757.33 389.47 763.73C389.47 763.73 392.02 756.83 399.53 758.42C407.05 760 411.78 759.11 420.63 769.58C429.47 780.06 436.43 791.9 436.43 791.9C436.43 791.9 432.63 762.5 419.89 743.66C407.16 724.82 396.36 716.68 392.94 714.04C389.52 711.4 380.01 704.74 380.01 704.74H380.03Z" fill="#E4FFA4"/>
<path d="M388.99 770.33C390.01 777.81 394.66 788.58 398.96 796.51C403.26 804.44 404.63 808.64 403.61 810.11C402.59 811.58 399.53 810.59 396.69 809.39C393.85 808.19 386.6 807.75 382.57 809.66C382.57 809.66 389.46 806.94 399.08 814.74C408.69 822.53 417.86 834.42 418.02 834.37C418.18 834.32 417.44 816.31 405.44 797.95C393.44 779.6 388.99 770.33 388.99 770.33Z" fill="#E4FFA4"/>
<path d="M459.24 906.97C459.24 906.97 494.13 909.53 523.86 889.62C553.59 869.71 563.4 848.01 569.81 822.35C576.7 794.77 570.37 763.62 563.1 739.16C555.82 714.7 568.65 764.75 565.28 795.38C561.92 826.01 550.81 853.47 532.13 869.16C513.45 884.85 502.1 895.41 489.1 898.66C476.09 901.91 459.24 906.97 459.24 906.97Z" fill="#E4FFA4"/>
<path d="M567.24 645.6C567.24 645.6 558.62 660.9 557.39 679.03C556.16 697.17 557.25 718.37 561.85 734.82C566.44 751.27 553.82 720.79 551.63 708.85C549.44 696.9 547.26 684.05 551.68 669.92C556.1 655.79 563.49 645.6 563.49 645.6H567.25H567.24Z" fill="#E4FFA4"/>
<path d="M773.86 913.54C771.37 910.13 768.96 905.22 768.59 897.5C768.22 889.78 769.93 884.21 772.1 882.04C773.86 880.28 774.41 879.99 774.41 879.99C774.41 879.99 774.41 883.19 774.78 886.91C774.78 886.91 773.67 890.35 772.93 896.22C772.19 902.09 773.85 913.55 773.85 913.55L773.86 913.54Z" fill="#E4FFA4"/>
<path d="M742.94 858.55C742.94 858.55 739.29 869.25 739.06 883.15C738.83 897.06 741 907.58 741 907.58C741 907.58 732.3 897.73 732.79 884.03C733.28 870.33 736.52 863.55 739.73 861.05L742.94 858.55Z" fill="#E4FFA4"/>
<path d="M34.51 544.94C43 520.25 61.42 470.18 88.97 421.11C116.52 372.04 144.41 338 173.74 303.83C203.07 269.65 229.04 251.99 240.02 245.24C251 238.49 257.88 240.88 261.28 243.74C264.68 246.6 268.77 249.97 270.76 264.92C272.75 279.87 274 252.27 270.09 241.67C266.18 231.07 256.09 228.62 247.18 232.28C238.27 235.94 207.57 258.27 187.39 280.39C167.21 302.5 136.64 340.87 116.69 368.98C96.74 397.08 84.5 418.88 73.62 441.11C62.74 463.34 57.38 484.41 48.93 500.45C40.47 516.49 34.51 544.94 34.51 544.94Z" fill="#E4FFA4"/>
<path d="M187.24 370.01C171.98 383.96 140.56 428.52 130.24 454.92C119.92 481.32 111.84 514.59 111.84 514.59C111.84 514.59 124.19 483.37 133.69 463.57C143.19 443.77 154.69 418.83 162.45 407.63C170.21 396.43 180.24 382.38 180.24 382.38L187.24 370.01Z" fill="#E4FFA4"/>
<path d="M194.05 474.61C194.05 474.61 196.53 439.2 200.76 415.26C204.99 391.32 218.11 348.76 228.38 332.15C228.38 332.15 224.11 355.48 218.52 370.95C212.94 386.42 201.6 432.94 201.6 432.94L194.04 474.61H194.05Z" fill="#E4FFA4"/>
<path d="M325.97 926.16C325.34 933.67 323.98 945.23 322.17 949.77C320.36 954.31 317.29 951.47 314.91 949.09C312.53 946.71 303.69 944.78 298.47 953.4C293.25 962.02 297.16 961.56 297.16 961.56C297.16 961.56 301.53 949.66 306.18 950.79C310.83 951.92 313.21 955.12 316.5 963.44C319.79 971.76 320.43 976.79 320.43 976.79C320.43 976.79 325.73 966.46 327.01 955.67C328.29 944.89 327.01 940.85 327.01 937.18C327.01 933.51 325.97 926.15 325.97 926.15V926.16Z" fill="#E4FFA4"/>
<path d="M425.18 614.18C436.28 622.92 445.31 633.47 452.26 647.12C459.21 660.77 460.07 675.46 460.07 675.46C460.07 675.46 454.8 661.27 450.51 655.11C446.22 648.95 444.43 650.5 443.59 651.8C442.75 653.1 444.24 654.99 445.42 657.47C446.6 659.95 449.79 664.9 453.11 675.01C456.44 685.12 456.7 693.27 456.7 693.27C456.7 693.27 450.84 674.04 442.01 662.22C433.18 650.4 420.91 639.56 411.76 634.78C411.76 634.78 417.69 635.61 425.18 638.73C432.67 641.85 436.1 644.5 438.39 643.95C440.68 643.41 439.26 638.65 437.04 633.53C434.02 626.56 425.19 614.17 425.19 614.17L425.18 614.18Z" fill="#E4FFA4"/>
<path d="M388.34 956.57C413.9 960.1 446.2 957.69 467.03 952.37C489.39 946.65 504.8 933.78 504.8 933.78C504.8 933.78 493.12 938.23 477.91 939.71C462.71 941.19 453.62 939.71 453.62 939.71C453.62 939.71 469.38 937.67 484.4 931.92C499.42 926.17 514.55 913.57 514.55 913.57C514.55 913.57 504.32 918.54 476.84 924.39C449.36 930.24 418.08 926.14 404.05 922.64C404.05 922.64 414.25 928.11 423.31 931.52C432.37 934.94 437.46 937.07 437.6 940.09C437.83 945 430.05 948.64 416.26 951.68C403.38 954.52 388.34 956.57 388.34 956.57V956.57Z" fill="#E4FFA4"/>
<path d="M391.97 564.33C358.09 550.8 337.88 506.45 322.9 475.16C299.15 422.8 285.14 366.6 277.07 309.82C273.99 289.78 271.38 269.67 268.78 249.62C268.43 246.64 267.71 243.25 266.13 240.76C261.75 233.47 252.1 233.67 244.84 236.82C240.66 238.52 237.09 241.11 233.42 243.83C158.8 302.24 102.08 382.29 63.7801 468.54C36.9901 529.31 16.93 593.48 8.01004 659.39C6.81004 668.81 5.85002 678.25 5.21002 687.72C5.21002 687.72 1.68003 687.13 1.69003 687.13C9.35003 660.21 18.7601 633.85 30.3401 608.37C39.0801 589.3 48.7101 570.17 62.1501 553.8C66.8401 548.33 71.6 542.93 77.39 538.56C83.83 533.61 93.34 531.69 100.1 537.5C105.19 541.81 107.45 547.83 108.46 554.06C110.33 567.82 111.76 585.76 113.16 599.69C115.56 627.36 119.44 654.92 125.87 681.95L121.69 682.24C122.82 670.08 124.24 658.01 125.94 645.94C131.16 609.68 138.43 573.3 154.55 540.07C168.83 512.2 193.4 507.63 197.95 543.89C201.42 571.08 203.17 598.44 205.29 625.7L201.76 625.54C203.63 614.11 205.89 602.8 208.51 591.53C212.52 574.68 217.25 557.68 224.99 542.07C228.88 534.22 233.1 526.18 239.42 519.85C246.18 512.81 255.84 510.73 265.24 512.79C271.29 514.1 276.48 517.55 281.24 521.21C294.81 532 306.79 544.38 320.55 554.87C334.21 565.11 350.01 572.3 365.87 578.56C371.21 580.63 376.57 582.63 382.09 584.37C376.29 583.96 370.61 582.6 365.04 580.99C348.34 575.94 332.56 568 318.29 557.97C304.34 547.79 291.95 535.46 278.57 524.67C271.9 519.28 264.81 515.74 256.34 516.39C247.98 516.72 241.79 522.41 237.11 529.19C224.35 548.14 217.71 570.4 212.31 592.44C208.43 608.11 205.28 625.21 202.8 641.21C200.8 612.04 198.68 582.47 195.1 553.49C194.56 549.34 193.82 544.25 192.95 540.19C191.22 532.08 186.32 518.96 175.86 523.79C167.99 527.3 162.96 534.78 158.91 542.37C153.42 552.97 149.48 564.46 145.79 575.85C138.23 598.84 133.84 622.66 130.32 646.58C127.88 662.77 126.17 679.84 124.55 696.15C119.16 671.33 114.13 646.12 111.43 620.9C108.89 599.36 107.46 577.58 103.53 556.25C103.14 553.88 102.48 551.45 101.58 549.26C97.59 539.33 89.39 536.75 80.7 543.21C75.4 547.25 70.7701 552.34 66.3401 557.35C52.9801 573.15 43.23 591.55 34.37 610.25C20.23 640.47 9.07004 672.49 0.540039 704.75L1.62003 687.52C3.41003 658.87 8.01003 630.47 14.34 602.49C44.5 472.47 107.67 347.46 207.1 256.79C214.23 250.35 221.59 244.17 229.34 238.36C233.3 235.4 237.53 232.37 242.22 230.48C252.25 226.11 265.85 226.57 272.02 237.22C274.86 241.78 275.33 247.15 275.96 252.29C277.14 261.77 278.17 271.23 279.28 280.69C286.1 346.78 299.12 412.7 326.55 473.53C338.26 499.3 351.2 525.02 370.54 545.92C376.96 552.83 383.87 559.37 391.97 564.33V564.33Z" fill="#0E1016"/>
<path d="M339.66 524.44C326.32 504.71 315.55 483.01 305.86 461.26C280.66 403.67 264.92 342.46 253.68 280.76C251.74 274.35 249.14 264.77 241.09 265.55C229.81 266.62 220.11 274.06 211.55 281.16C166.59 320 130.66 368.53 99.9199 419.13C87.6899 439.56 76.32 460.53 65.7 481.91C84.46 437.97 107.73 395.75 136.08 357.22C157.61 328.45 181.22 301.04 208.33 277.33C214.76 272.03 221.36 266.43 228.85 262.44C246.84 253.07 256.79 260.23 259.69 279.46C262.21 294.1 264.64 309.04 267.62 323.52C274.79 358.47 284.81 392.83 296.38 426.56C308.29 460.18 322.16 493.32 339.67 524.44H339.66Z" fill="#0E1016"/>
<path d="M224.69 474.61C220.42 443.69 220.09 412.36 221.11 381.2C221.43 370.79 221.94 360.43 222.8 349.99L226.51 350.77C216.52 381.18 207.28 411.89 200.49 443.19C198.21 453.62 196 464.08 194.06 474.6C195.3 463.97 196.96 453.39 198.91 442.87C205.78 405.39 216.74 368.41 228.39 332.14C223.41 379.34 221.88 427.21 224.7 474.6L224.69 474.61Z" fill="#0E1016"/>
<path d="M139.12 500.39C143.78 479.77 149.73 459.54 155.82 439.29C159.01 429.19 162.76 419.3 166.71 409.48C170.65 399.66 174.91 390 179.34 380.38L181.68 381.83C151.37 421.92 131.04 468.43 111.84 514.59C115.75 502.64 120.43 490.95 125.31 479.37C139.97 444.58 156.63 410.33 179.51 380.12L187.25 370.02L181.86 381.57C173.11 400.68 164.85 420.13 158.07 440.03C151.51 460.05 145.86 480.41 139.13 500.4L139.12 500.39Z" fill="#0E1016"/>
<path d="M640.16 251.83C645.12 249.62 650.99 248.55 656.27 247.37C657.61 247.05 660.17 246.6 661.34 246.11C662.68 245.55 663.82 244.53 664.62 243.19C667.14 238.68 666.57 233.17 665.76 228.1C665.11 224.57 664.23 220.95 663.28 217.46C658.54 199.8 649.45 183.66 638.3 169.23C624.97 151.76 608.65 136.51 590.69 123.91L593.11 121.61C599.81 134.2 605.65 147.11 610.24 160.61C611.77 165.12 613.14 169.7 614.45 174.37C614.86 175.84 614.21 175.09 612.74 175.51C611.08 176 611.23 175.8 611.12 174.08C611.01 172.91 608.78 172.98 608.61 171.82C606.83 159.23 602.01 147.15 596.65 135.67C593.78 129.44 589.98 123.01 586.45 117.15C604.43 128.25 620.78 141.98 634.65 157.89C651.99 177.94 665.74 200.87 669.47 227.52C670.49 234.91 670.8 245.68 662.59 249.25C660.55 249.97 658.86 250.14 656.86 250.52C651.31 251.39 645.83 252.16 640.15 251.83H640.16Z" fill="#0E1016"/>
<path d="M539.99 148.37C557.57 141.03 588.78 140.7 605.8 149.57C606.18 149.75 606.01 150 605.88 150.59C605.68 151.42 604.96 152.54 604.36 152.97C604.24 153.03 604.19 152.94 604.11 152.92C603.3 152.57 601.74 151.95 600.9 151.6C587.24 145.84 571.66 145.2 557.18 147.82C551.99 148.83 546.71 150.38 542.03 152.74C539.09 154.14 536.99 149.76 539.99 148.37V148.37Z" fill="#0E1016"/>
<path d="M566.15 155.12C589.73 156.54 616.43 167.04 626.15 190.17C622.1 185.66 618.24 181.27 613.64 177.63C611.6 176.02 608.68 173.94 606.59 172.48C594.43 164.02 580.51 158.77 566.16 155.12H566.15Z" fill="#0E1016"/>
<path d="M515.04 130.01C526.18 129.04 537.44 130.14 548.43 132.11C559.34 134.13 570.17 137.8 579.9 143.2C582.64 144.71 581.7 146.45 578.86 145.06C578.84 145.05 577.45 145.83 577.43 145.82L576.76 145.23L574 145.35C561.02 138.26 546.35 134.66 531.7 133.1C526.44 132.56 521.13 132.31 515.85 132.45L514.98 132.22C513.84 131.95 513.88 130.22 515.04 130.01Z" fill="#0E1016"/>
<path d="M623.55 147.6C644.23 160.3 663.2 176.56 676.62 196.97C680.57 203.28 684.87 210.34 684.53 218.26C684.25 223.93 679.79 228.03 675.54 231.14C673.5 232.65 671.43 233.97 669.11 235.17C668.17 235.65 667.02 235.29 666.53 234.35C666.04 233.41 666.42 232.27 667.33 231.78C671.21 229.48 675.18 226.52 678.14 223.33C687.98 212.61 663.62 184.57 655.77 176.46C646.08 166.6 635.28 157.81 623.77 150.13L623.01 149.63L622.63 149.38C622.45 149.26 622.23 149.1 622.05 148.95C621.36 148.41 621.95 147.2 622.8 147.44L623.54 147.61L623.55 147.6Z" fill="#0E1016"/>
<path d="M543.45 219.94C556.98 210.44 568.7 211.97 583.25 218.05C591.54 221.55 599.43 225.78 607.57 229.44C612.87 231.7 618.29 234.12 624.06 234.37C631.21 234.61 635.57 229.36 636.19 221.99C640.09 233.78 629.44 241.17 618.65 238.8C609.6 236.97 601.43 232.51 593.1 228.86C584.12 224.91 575.33 220.21 565.79 218.05C558.24 216.28 550.98 217.97 543.45 219.94V219.94Z" fill="#0E1016"/>
<path d="M590.59 225.6C581.19 225.14 571.67 227.34 562.96 230.7L564.36 227.36C569.67 238.95 579.57 249.07 590.77 255.04C593.65 256.52 596.71 257.56 599.85 258.17C609.18 260.5 623.21 254.82 630 263.33C630.05 263.35 626.34 264.57 626.35 264.58C625.87 254.66 622.91 244.32 616.5 236.54C616.12 236.11 616.17 235.45 616.62 235.09C617.03 234.76 617.63 234.81 617.98 235.2C619.79 237.18 621.5 239.27 622.87 241.58C627.08 248.53 629.49 256.31 630.37 264.41L631.04 271.35C631.04 271.35 626.71 265.65 626.7 265.65C625.37 263.45 622.55 262.38 619.42 262.01C612.88 261.26 605.92 262.49 599.3 261.05C585.66 258.13 574.75 248.32 565.94 238.04C563.1 234.64 560.57 230.9 558.55 226.91C561.83 225.87 565.34 224.94 568.63 224.36C575.93 223.02 583.46 222.47 590.82 223.62C592.08 223.82 591.85 225.71 590.59 225.61V225.6Z" fill="#0E1016"/>
<path d="M596.15 258.76C596.15 258.76 597.41 253.82 600.82 248.85C604.23 243.88 609.94 240.66 609.94 240.66C609.94 240.66 610.23 247.82 609.21 252.94C608.19 258.06 607.16 260.29 607.16 260.29C607.16 260.29 603.28 260.3 600.82 259.82C598.36 259.34 596.15 258.76 596.15 258.76V258.76Z" fill="#0E1016"/>
<path d="M718.72 296.83C700.05 281.07 680.8 264.11 662.53 247.85C661.61 247.03 662.8 245.53 663.81 246.31C677.96 257.3 693.7 269.78 707.67 280.99C712.5 284.89 717.32 288.81 722.11 292.76C723.55 294 721.68 293.86 720.65 294.84C719.79 295.66 718.71 296.83 718.71 296.83H718.72Z" fill="#0E1016"/>
<path d="M695.01 360.47C695.01 360.47 697.57 360.62 700.74 362.74C703.91 364.86 711.47 364.74 714.72 360.72C719.65 354.63 717.24 349.04 724.64 346.37" stroke="#0E1016" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round"/>
<path d="M697.72 361.16C697.72 361.16 704.39 365.86 709.78 363.66C715.17 361.46 716.98 357.71 717.69 354.64C718.4 351.57 718.71 350.13 719.43 349.38C720.15 348.63 725.46 346.95 723.22 345.59C719.69 343.45 716.14 338.12 716.14 338.12C716.14 338.12 712.93 347.58 707.65 352.31C702.37 357.04 697.46 359.67 697.46 359.67L697.72 361.16Z" fill="#0E1016"/>
<path d="M731.18 319.57C733.29 315.24 735.97 309.19 739.07 306.6C742.17 304.02 744.35 304.85 745.13 306.6C746 308.58 744.57 310.3 741.95 311.47C739.8 312.43 738.27 313.52 736.27 315.01C734.55 316.29 731.18 319.56 731.18 319.56V319.57Z" fill="#0E1016"/>
<path d="M691.49 322.64C689.09 319.24 684.72 314.79 679.85 312.61C674.98 310.43 672.41 312.79 672.19 315.3C672.01 317.32 674.75 319.55 678.48 319.37C682.21 319.19 685.57 320.59 688 321.42C690.43 322.25 691.49 322.64 691.49 322.64V322.64Z" fill="#0E1016"/>
<path d="M668.37 327.92C653.27 322.99 652.79 303.72 666.54 296.68C678.34 291.11 694.47 296.6 700.2 308.12C692.68 299.62 678.9 296.5 668.92 301.44C662.92 304.24 659.08 311.07 660.68 317.68C661.73 321.95 664.76 325.28 668.37 327.93V327.92Z" fill="#0E1016"/>
<path d="M666.99 300.14L658.84 288.31C656.5 284.81 653.07 279.88 650.95 276.3C652.52 278.1 654.18 279.82 655.83 281.54C658.97 284.81 667.62 293.5 670.94 296.83C672.34 298.32 671.27 298.95 670.03 299.74C669.01 300.39 668.01 301.49 667 300.14H666.99Z" fill="#0E1016"/>
<path d="M522.9 233.47C527.55 241.95 533.98 249.28 541.12 255.7C545.1 259.24 549.54 262.5 553.67 265.95C546.18 266.95 542.36 268.37 531.08 267.14L533.46 264.05C533.79 264.54 534.13 265.15 534.54 265.65C541.08 274.14 550.88 279.47 560.98 282.7C569.24 285.22 577.96 286.8 586.61 287.24C594.12 287.71 601.51 286.86 609.09 287.77C613.87 289.09 617.45 292.64 621.14 295.59C627.93 301.48 634.29 307.83 640.57 314.23C644.91 318.69 649.23 323.15 653.45 327.74C653.45 329.73 653.72 331.89 654.14 333.88C656.7 347.6 667.98 356.84 681.4 359.16C681.79 367.38 681.1 375.6 678.73 383.49L676.55 382.25C677.97 380.73 679.35 379.09 680.64 377.44C684.64 372.52 687.04 366.65 689.19 360.74C689.19 360.74 689.53 359.75 689.54 359.74C703.15 359.41 712.39 346.77 716.24 334.82L717.31 337.15C720.09 343 726.73 346.72 733.15 346.25C734.74 353.12 732.95 360.06 731.49 366.76C731.52 366.86 729.29 365.6 729.28 365.63C735.48 358.7 738.94 349.68 740.34 340.58C746.65 329.2 753.08 312.43 747.92 299.73C745.87 294.69 741.03 291.48 735.5 291.31C728.65 290.9 722.83 294.7 717.1 298.03C720.64 291.42 728.1 287.52 735.57 287.28C749.08 287.12 755.39 299.63 754.34 311.73C753.41 322.6 749.37 332.94 743.6 342.12L743.85 341.46C742.85 346.29 741.45 350.88 739.39 355.35C736.79 361.13 732.56 366.07 728.14 370.58L728.94 366.23C729.88 360.4 731.18 354.28 729.95 348.44L731.71 349.79H731.67C726.15 349.8 721.02 346.84 717.81 342.5C716.76 341.05 715.94 339.56 715.39 337.76H717.29C713.27 349.71 704.16 361.58 690.76 362.86L692.18 361.8C689.8 367.93 687.05 374.1 682.75 379.13C680.6 381.89 677.56 384.81 675.13 387.36C675.87 384.25 676.78 380.45 677.24 377.33C678.14 371.9 678.07 366.28 677.65 360.82L679.18 362.54C671.14 360.94 663.65 357.14 658.07 351.04C652.52 344.89 649.53 336.75 649.32 328.51L649.85 329.91C639.9 319 629.7 308.21 618.51 298.59C615.31 296.08 612.2 293.03 608.38 291.82C604.16 291.35 599.79 292.02 595.42 292.22C586.36 292.69 577.12 291.69 568.26 289.74C553.38 286.48 537.84 279.49 529.6 266.03L527.36 261.9L531.99 262.94C537.9 264.28 544.28 263.94 550.33 263.71L549.82 266.2C546.39 264.83 543.27 262.81 540.42 260.49C534.8 255.81 530.31 249.88 526.8 243.52C525.12 240.32 523.6 236.98 522.86 233.45L522.9 233.47Z" fill="#0E1016"/>
<path d="M680.73 378.53C688.47 380.17 696.45 379.18 704.03 377.17C711.43 375.35 719.09 372.4 723.2 365.62C725.92 361.19 728.32 356.19 730.98 351.64C731.31 351.02 732.09 350.79 732.71 351.13C733.29 351.45 733.51 352.17 733.23 352.76C732.09 355.21 730.93 357.65 729.74 360.07C728.62 362.29 727.28 365.28 725.92 367.35C719.97 377.42 707.4 380.14 696.73 381.92C691.23 382.7 685.56 382.47 680.15 381.2C679.41 381.03 678.96 380.29 679.13 379.56C679.3 378.84 680.01 378.39 680.73 378.53V378.53Z" fill="#0E1016"/>
<path d="M504.49 248.3C513.12 260.4 520.21 273.87 527.3 286.78C536.33 301.79 550.26 310.86 568.07 309.39C577.76 309.07 587.85 307.62 597.51 306.33C607.28 304.73 612.59 306.32 619.76 313.28C633.81 327.68 644.56 344.74 656.15 361.01C661.78 368.78 669.45 375.21 678.76 377.86C680.56 378.36 680.05 381.31 677.94 380.84C661.2 376.31 653.7 364.28 644.42 351.08C638.66 343.02 632.74 335.07 626.44 327.43C621.16 321.44 613.54 310.88 605.15 310.57C601.3 310.47 594.79 311.75 590.78 312.27C583.35 313.36 575.82 314.35 568.28 314.72C560.48 315.31 552.28 314.36 545.18 310.87C535.61 306.4 528.11 298.32 523.04 289.26C521.18 285.96 519.69 282.39 518.08 279.05C513.45 268.87 508.75 258.72 504.48 248.31L504.49 248.3Z" fill="#0E1016"/>
<path d="M693.26 368.72C696.91 369.54 700.47 370.16 704.13 370.36C707.79 370.66 711.15 370.85 714.86 370.01C709.1 376 698.94 373.68 693.26 368.72V368.72Z" fill="#0E1016"/>
<path d="M551.95 187.15C546.13 173.19 541.66 158.74 535.51 145.06C535.16 144.32 534.57 143.27 534.12 142.68C534.14 142.68 533.98 142.56 533.86 142.46C533.76 142.36 533.51 142.23 533.35 142.11C527.4 138.87 520.26 136.81 513.75 134.38C509.18 132.75 504.6 131.09 500.03 129.36C481.71 122.57 464.07 113.62 447.2 103.8C421.88 89.04 397.44 72.34 376.9 51.21L380.22 49.6C381.07 54.73 382.78 60.11 384.51 65.06C395.87 95.63 416.53 122.15 441.03 143.42C442.63 144.72 446.94 148.24 448.6 149.59C450.32 150.85 454.79 154.16 456.44 155.4C458.26 156.72 462.61 159.6 464.5 160.91C482.85 172.74 502.45 182.39 522.9 190.16C514.1 189.02 505.53 186.52 497.19 183.49C445.55 164.26 398.93 118.62 380.46 66.47C378.75 61.11 377.16 55.8 376.34 50.09L375.49 44.3C390.26 59.55 407.19 72.6001 425.03 84.1701C449.39 99.8301 475.02 113.43 502.37 123.06C511.44 126.44 520.58 129.47 529.77 133.26C532.35 134.4 535.24 135.57 537.79 137.52C539.87 139.15 540.82 141.89 541.83 144.23C546.96 158.15 550.06 172.49 551.95 187.14V187.15Z" fill="#0E1016"/>
<path d="M448.75 130.95C460.21 141.49 473.47 149.97 487.52 156.54C497.11 161.16 506.69 165.5 517.17 167.97C515.5 168.14 513.82 168.1 512.14 168C487.47 166.09 463.95 149.84 448.74 130.95H448.75Z" fill="#0E1016"/>
<path d="M641.11 161.28C641.65 155.48 642.37 149.75 644.03 144.13C646.65 136 648.09 128.37 640.05 123.19C618.88 108.96 599.25 92.19 583.29 72.21C578.61 66.13 573.47 58.55 569.3 52.12C560.47 38.43 552.21 24.44 544.56 10.02L548.22 9.02C549.69 33.74 555.65 58.27 564.35 81.38C568.54 92.98 573.6 104.39 579.13 115.47C584.67 126.39 590.59 137.37 598.39 146.83C599.12 147.57 599.09 148.78 598.3 149.46C597.58 150.1 596.48 150.05 595.82 149.37C594.6 148.12 593.59 146.95 592.57 145.7C589.56 142 586.86 138.07 584.27 134.08C577.51 123.41 571.77 112.17 566.64 100.66C564.16 94.98 561.53 88.98 559.51 83.13C552.37 62.23 548.26 40.5 545.37 18.67C544.56 13.17 544.1 5.6 543.54 0L548.01 8.24C559.28 29.56 571.63 50.67 586.14 69.98C601.12 88.88 619.84 104.72 639.67 118.32C641.17 119.31 643.51 120.77 644.84 122.04C651.93 128.12 650.45 137.38 648.01 145.31C647.34 147.62 646.99 150.04 646.69 152.47C646.22 156.39 646.04 160.45 646.1 164.37C646.03 167.95 641.02 162.25 641.11 161.27V161.28Z" fill="#0E1016"/>
<path d="M586.56 312.01C567.05 338.8 561.04 378.45 559.94 411C558.38 460.61 566.29 508.56 587.13 553.81C598.13 578.29 610.32 602.42 620.94 627.14C621.42 628.25 618.44 633.65 617.91 632.41C612.39 619.5 606.66 606.67 600.75 593.94C582.29 555.97 564.6 516.66 559.16 474.35C556.09 453.32 555.18 432.09 555.63 410.87C556.26 389.61 558.83 368.35 564.91 347.88C568.49 335.93 573.16 324.15 579.92 313.59C580.97 312.02 581.93 310.57 583.18 309.07C583.97 308.12 585.38 307.99 586.33 308.78C587.31 309.59 587.41 311.06 586.56 312V312.01Z" fill="#0E1016"/>
<path d="M514.11 266.45C485.87 308.03 468.64 356.86 461.51 406.48C459.76 419.24 458.73 432.11 458.49 444.99C457.69 483.8 459.86 522.76 466.61 561C473.26 599.33 484.58 636.65 497.13 673.43C519.52 734.28 536.42 798.59 542.35 863.24C542.35 863.24 541.3 866.42 539.62 866.58C537.94 866.75 536.44 865.53 536.28 863.85C534.18 838.03 530.32 812.35 525.11 786.96C512.8 722.96 485.08 663.45 469.55 600.25C459.88 562.26 454.54 523.19 453.01 484.05C449.74 423.11 456.99 367.39 481.52 311.09C489.46 292.95 498.49 275.31 509.93 259.07C509.93 259.07 514.76 265.52 514.12 266.45H514.11Z" fill="#0E1016"/>
<path d="M459.04 904.98C482.04 900.38 505.07 891.22 522.31 874.99C545.17 852 562.25 818.34 563.59 785.63C564 768.64 560.5 751.84 555.68 735.58C553.79 728.9 551.27 722.39 549.66 715.61C546.07 699.71 544.47 678.15 550.75 662.82C555.25 653.12 561.57 643.58 571.39 638.66L576.35 636.19C572.85 641.66 568.83 647.43 567.8 653.92L566.11 652.06C567.4 651.94 568.45 652 569.6 652.13C587.72 654.95 591.79 673.13 588.57 688.61C588.33 689.74 583.16 693.52 583.73 690.93C585.67 681.73 586.36 670.89 580.58 663C577.27 658.55 571.9 655.52 566.36 655.24L564.43 654.59L564.66 653.38C565.49 648.19 568.2 643.62 570.83 639.21L572.85 641.28C570.03 643.01 567.53 645.16 565.32 647.6C560.97 652.55 557.72 658.62 555.23 664.69C551.91 674.03 551.94 684.34 552.6 694.23C553.31 702.61 554.56 710.96 557.03 718.97C558.59 723.78 560.26 729.01 561.64 733.85C565.53 747.31 568.42 761.12 569.4 775.15C571.77 810.58 559.4 846.95 534.85 872.8C525.29 883.39 513.09 891.62 500 897.08C493.48 899.82 486.79 902.03 479.97 903.67C473.15 905.33 466.28 906.52 459.25 906.97C458.7 907 458.22 906.58 458.19 906.03C458.16 905.5 458.54 905.05 459.05 904.98H459.04Z" fill="#0E1016"/>
<path d="M573.75 729.21C573.63 722.22 574.18 715.26 575.58 708.38C577.12 701.4 579.86 694.91 583.01 688.53C588.63 677.02 597.32 667.47 608.84 661.63C610.44 660.79 612 660.03 613.75 659.32C615.22 658.72 612.67 660.91 613.27 662.37C613.83 663.74 614.19 665.72 613.18 666.35C608.59 669.23 604.08 672.39 600.22 675.95C591.79 683.71 585.5 694.17 580.62 704.61C577.2 712.41 575.38 720.75 573.74 729.21H573.75Z" fill="#0E1016"/>
<path d="M912.2 547.03C825.76 532.1 738.08 525.18 650.65 518.85L644.68 518.42C643.62 518.2 642.81 518.73 642.58 519.53L640.45 531.08C620.73 640.81 597.82 757.12 577.17 866.31L576.61 869.25L576.33 870.72C576.16 871.45 576.52 872.09 577.13 872.43C577.23 872.48 577.33 872.52 577.44 872.55C577.57 872.58 577.59 872.59 577.9 872.62L579.39 872.78C582.99 873.14 605 875.57 609.14 876L704.36 886.2C705.3 886.3 705.98 887.15 705.88 888.09C705.78 889.03 704.94 889.71 704 889.61C680.78 887.19 599.32 878.65 577.5 876.41C574.42 876.25 571.84 873.1 572.56 870.02C572.73 869.1 573.2 866.56 573.38 865.61C594.07 753.38 615.42 633.7 637.22 521.35L637.5 519.88L637.64 519.15C637.91 517.24 639.03 515.44 640.7 514.45C641.8 513.56 643.91 513.42 645.05 513.55C670.24 515.57 697.54 517.7 722.61 520.08C770.33 524.36 818.01 529.39 865.32 537.07C881.1 539.56 896.84 542.18 912.56 545.07C913.1 545.17 913.46 545.69 913.36 546.23C913.26 546.77 912.74 547.13 912.2 547.03V547.03Z" fill="#0E1016"/>
<path d="M953.48 463.55C939.98 430.93 924.04 399.22 912.58 365.76C911.4 362.5 908.33 353.97 907.19 350.81L906.97 350.19C907.13 350.7 907.88 350.82 908.2 350.31C908.23 350.25 908.26 350.21 908.27 350.15C908.27 350.15 908.26 350.19 908.25 350.21C908.12 350.77 907.82 351.88 907.68 352.46L897.28 393.51C897.06 394.48 896.08 395.08 895.12 394.83C894.17 394.59 893.59 393.61 893.84 392.66L904.63 351.64L904.97 350.36L905.14 349.72L905.22 349.4C905.75 347.05 909.14 346.88 909.9 349.15L910.12 349.77C911.22 352.91 914.23 361.51 915.37 364.77C926.52 398.31 942.12 430.04 955.33 462.79C955.54 463.3 955.29 463.88 954.78 464.09C954.27 464.3 953.69 464.05 953.48 463.54V463.55Z" fill="#0E1016"/>
<path d="M949.97 467.69C940.93 450.23 933.4 431.96 926.24 413.64C919.21 395.28 912.05 376.84 907.38 357.72C916.41 385.77 928.48 412.76 940.16 439.78C944.07 448.79 948.11 457.71 951.79 466.87C952.24 468.01 950.55 468.85 949.96 467.69H949.97Z" fill="#0E1016"/>
<path d="M912.51 545.05C924.16 546.1 940.02 547.76 950.06 540.9C954.47 537.77 957.93 533.4 960.51 528.64C964.04 522.2 965.64 515.05 964.16 507.73C963.59 504.09 962.66 500.48 961.61 496.9C961.45 496.37 961.75 495.81 962.29 495.65C962.81 495.5 963.35 495.79 963.52 496.29C964.73 499.88 965.84 503.53 966.57 507.31C969.27 518.93 964.2 531.03 956.18 539.42C950.75 545.29 942.83 547.95 935.01 548.33C927.34 548.9 919.74 548.13 912.26 547.05C911 546.84 911.21 544.95 912.52 545.07L912.51 545.05Z" fill="#0E1016"/>
<path d="M937.06 550.47C928.1 551.42 918.83 551.01 909.99 549.27C909.99 549.28 909.99 549.29 909.98 549.31C859.75 541.95 809.49 534.16 758.87 529.75C727.22 526.95 694.93 524.37 663.21 521.9C660.14 521.66 651.79 521.02 648.86 520.8L646.47 520.62L646.17 520.6C645.27 520.46 644.41 521.15 644.31 522.04C641.77 535.01 639.42 548 638.49 561.21C642.15 548.66 644.44 535.85 646.55 522.98C648.41 523.12 652.07 523.41 653.48 523.52C688.36 526.25 723.87 529.09 758.67 532.2C806.84 536.53 854.65 544 902.48 551.21C906 551.74 908.23 555.25 907.22 558.67C902.16 575.75 897.23 592.87 892.37 610.01C875.69 669.3 858.77 729.41 845.06 789.5C867.02 713.97 889.43 635.43 910.14 559.54C911.19 555.69 914.91 553.27 918.88 553.72C927.32 554.69 935.93 554.34 944.11 552C950.66 550.17 956.63 546.09 960.29 540.33C953.68 545.96 945.76 549.67 937.07 550.49L937.06 550.47Z" fill="#0E1016"/>
<path d="M739.86 922.63C722.13 924.3 705.57 910.66 702.69 893.22C701.71 887.48 702.29 881.44 703.61 875.88C704.47 872.21 705.42 868.38 707.04 864.92C709.24 859.78 713.05 855.12 716.91 851.05C716.59 853.58 715.92 857.75 715.79 860.24C715.44 864.01 715.94 867.71 716.81 871.36L715.3 869.9C716.95 870.3 718.43 870.75 719.97 871.25C724.54 872.77 728.98 874.9 732.84 877.8C734.66 879.12 732.75 881.87 730.87 880.61C729.76 879.82 728.29 878.72 727.09 878.09C722.89 875.73 718.09 874.16 713.34 873.46C711.91 867.7 711.77 861.74 712.87 855.92L715.76 857.3C711.25 862.84 708.68 869.71 707.37 876.71C705.76 883.65 705.57 890.86 707.92 897.59C712.07 910.19 724.28 919.71 737.63 918.94C738.54 918.89 743.46 922.16 740.85 922.49C740.85 922.49 740.17 922.57 739.84 922.6L739.86 922.63Z" fill="#0E1016"/>
<path d="M768.68 922.84C757.24 932.65 738.74 927.91 732.68 914.35C727.79 902.74 727.99 886.66 731.04 874.56C733.6 865.85 738.33 857.79 745.56 852.16L749.3 849.28C747.77 853.3 745.91 857.81 744.77 861.89C744.04 864.59 743.65 867.51 743.41 870.31L741.98 868.76C747.03 869.04 751.56 871.59 755.54 874.5C758.95 877.07 761.85 880.34 764.12 883.96C764.44 884.48 764.31 886.36 763.32 886.92C762.33 887.48 762.02 888.55 761.47 887.56C757.63 880.03 750.53 873.1 741.99 871.63L740.43 871.51L740.56 870.08C740.99 864.06 742.83 858.24 745.14 852.72L747.17 854.27C743.98 856.87 741.4 860.3 739.26 863.9C733.67 873.04 732.4 883.88 732.7 894.39C733.14 904.29 734.23 916.3 743.74 921.73C750.56 925.87 760.31 925.28 765.61 919.5C767.9 917.17 771.2 920.77 768.68 922.86V922.84Z" fill="#0E1016"/>
<path d="M967.59 491.45C967.23 490.53 966.08 490.14 965.2 490.76C964.61 491.18 964.43 491.98 964.69 492.65C967.81 500.63 970.57 508.99 968.87 517.55C967.65 524.38 963.27 536.21 961.27 542.71C927.6 636.55 887.35 802.17 861.38 900.43C860.82 901.31 859.57 902.5 858.65 903.19C849.68 910.02 837.2 910.05 827.13 905.71C824.63 904.74 820.94 901.88 818.91 899.23C818.35 898.5 818.14 897.56 818.37 896.66C818.37 896.66 818.37 896.66 818.37 896.65C822.55 879.6 825.49 862.28 828.51 844.99C827 849.44 825.64 853.93 824.36 858.44C821.16 869.53 818.27 880.72 815.99 892.04C815.64 893.79 813.4 897.84 808.21 897.4C805.83 897.2 803.48 896.76 801.11 896.47C800.41 894.25 799.58 892.06 798.68 889.94C795.72 883.53 791.18 876.36 783.82 874.77C783.34 873.24 779.59 861.17 779.59 861.17L778.03 856.17L776.19 861.1C774.76 865.13 773.65 869.08 772.5 873.14C767.58 875.14 763.45 878.75 761.21 883.65C758.06 891.07 757.51 899.33 758.84 907.21C759.29 909.81 759.91 912.4 760.71 914.92C765.3 928.46 781.34 935.41 794.58 930.87C805.27 927.37 812.48 915.91 817.15 905.56C822.38 909.89 829.17 911.6 835.68 912.89C844.02 914.1 852.83 912.55 859.93 907.86C862.21 906.24 864.16 904.8 865.63 902.19C884.62 831.06 926.57 662.18 948.86 594.49C951.52 586 966.54 538.39 968.74 531.15C974.11 514.94 974.14 507.45 967.59 491.45V491.45ZM804.35 918.72C799.96 924.27 793.61 927.86 786.46 927.67C777.41 927.86 768.43 921.82 765.52 913.28C763.45 906.49 762.41 899.23 763.59 892.2C764.69 884.23 767.99 879.01 775.44 875.64C776.24 872.9 777.05 870.1 777.92 867.34C779.39 871.57 781.14 876.63 781.14 876.63L781.35 877.22C789.67 880.17 793.99 888.62 797.7 896.19C801.11 903.15 801.43 907.12 802.94 911.05C803.34 907.32 802.94 903.59 802.09 899.95C802.26 899.99 802.45 900.03 802.65 900.07C808.78 901.32 811.74 908.35 808.26 913.55C807.05 915.35 805.76 917.08 804.36 918.73L804.35 918.72Z" fill="#0E1016"/>
<path d="M865.83 386.9C866.77 369.54 871.27 352.64 875.87 335.95L877.47 340.53C887.95 370.54 899.4 400.33 913.11 429.03C917.33 437.83 923.17 448.56 927.95 457.15C937.15 473.89 946.81 490.42 956.97 506.6C960.69 511.95 962.08 519.49 958.9 525.31C960.18 520.14 959.33 514.86 956.83 510.39C943.44 489.94 930.74 468.98 919.01 447.52C908.79 428.81 900.01 409.27 892.12 389.51C885.8 373.67 879.84 357.72 874.59 341.48C874.59 341.48 877.5 341.42 877.49 341.43C874.32 352.62 871.52 364 869.7 375.49C869.12 379.32 868.64 383.17 868.48 386.99C868.39 388.73 865.8 388.61 865.83 386.89V386.9Z" fill="#0E1016"/>
<path d="M846.49 380.06C847.15 377.3 853.86 349.76 854.34 347.78C857.04 357.03 860.24 366.21 863.71 375.22C869.92 391.32 877.2 407.04 885.61 422.11C897.18 442.43 911.13 461.29 925.92 479.39C933.63 488.86 941.73 498.01 950.05 506.94C952.89 509.83 955.71 513.31 956.31 517.45C954.82 513.64 951.9 510.7 948.93 508.03C940.26 499.35 932.13 490.17 924.19 480.82C903.15 455.82 884.18 428.7 870.7 398.82C863.88 383.91 857.87 368.68 853.24 352.92L855.68 352.89L848.78 381.97C848.44 383.24 846.18 381.32 846.48 380.06H846.49Z" fill="#0E1016"/>
<path d="M650.82 515.66C650.94 514.76 653.32 494.57 653.34 494.49C660.22 494.02 667.03 494.11 673.8 494.25C716.72 495.48 759.4 501.24 801.6 508.82C837.76 515.39 873.71 523.15 909.11 533.07C915.05 534.72 920.75 536.84 926.68 537.85C932.68 539.09 938.65 538.74 944.77 538.61C938.94 540.87 932.46 540.61 926.35 539.91C921.13 539.34 913.56 536.91 908.42 535.46C873.11 525.62 837.18 518.01 801.14 511.39C759.1 503.95 716.51 498.5 673.76 498.39C667.7 498.42 661.54 498.55 655.62 499.01L657.76 496.94L655.73 516.2C655.33 519.42 650.52 518.94 650.83 515.65L650.82 515.66Z" fill="#0E1016"/>
<path d="M660.09 496.26C660.19 495 661.14 485.47 661.21 484.55C674.48 485.1 687.67 486.07 700.86 487.25C738.83 490.62 776.8 496.24 814.01 504.61C848.16 512.11 882.34 519.92 915.74 530.38C920.91 532.11 926.1 533.94 931.59 534.25C942.81 535.32 951.02 532.55 956.31 521.93C953.87 534.36 942.72 537.82 931.38 536.87C925.67 536.59 920.26 534.79 914.91 532.95C881.62 522.44 847.5 514.84 813.4 507.41C776.29 499.15 738.44 493.63 700.57 490.41C687.93 489.32 675.26 488.42 662.62 487.91L664.31 486.41L663.61 496.57C663.43 498.9 659.9 498.66 660.08 496.28L660.09 496.26Z" fill="#0E1016"/>
<path d="M665.99 486.24C666.1 485.35 667.53 476.13 667.62 475.45C692.02 476.86 716.3 479.72 740.51 482.88C799.98 490.39 858.46 504.93 914.72 525.54C920.3 527.52 925.83 529.55 931.76 530.1C937.25 530.64 943.72 530.29 947.53 525.95C947.88 525.52 948.53 525.47 948.95 525.83C949.37 526.19 949.41 526.81 949.06 527.23C944.73 532.26 937.64 532.76 931.54 532.21C925.48 531.7 919.66 529.64 913.97 527.66C846.5 503.19 775.77 489.16 704.48 481.81C692.6 480.54 680.7 479.37 668.81 478.47L670.41 477.19L668.93 486.68C668.64 488.59 665.71 488.19 665.98 486.23L665.99 486.24Z" fill="#0E1016"/>
<path d="M954.27 504.94C958.67 498.89 963.2 492.98 967.97 487.21C972.84 481.44 977.81 475.14 984.95 472.03C999.68 464.91 1017.31 472.87 1024.3 487.01C1024.86 488.17 1023.15 489.07 1022.53 487.93C1020.85 484.76 1018.51 481.94 1015.82 479.56C1004.45 469.18 988.27 469.83 977.65 480.91C972.61 486.23 968.03 492.12 963.55 497.95C961.29 500.89 959.05 503.85 956.86 506.84C956.18 507.75 955.68 507.15 955.04 506.57C954.5 506.08 953.69 505.73 954.26 504.94H954.27Z" fill="#0E1016"/>
<path d="M997.61 469.75C1007.64 471.62 1005.3 489.77 1004.05 497.11C1002.42 505.32 999.38 517.1 997.38 525.35C992.85 544.03 988.2 562.77 986.07 581.87C981.76 613.91 989.97 636.79 1017.42 654.93C1025.32 660.22 1033.83 664.62 1042.46 668.89C1043.44 669.38 1043.85 670.57 1043.36 671.55C1042.87 672.54 1041.67 672.94 1040.68 672.45C1032.03 668.15 1023.36 663.65 1015.23 658.18C998.04 646.95 985.8 631.48 982.37 610.81C979.64 591.22 983.78 571.83 987.64 552.8C990.61 539.12 994.5 524.19 997.98 510.58C1000.27 501.36 1003.06 492.06 1002.67 482.52C1002.38 478.48 1001.93 472.73 997.28 471.71C996.03 471.52 996.28 469.55 997.6 469.74L997.61 469.75Z" fill="#0E1016"/>
<path d="M1046.37 672.02C1049.19 673.55 1053.37 673.93 1055.36 676.16C1056.62 677.57 1057.75 679.07 1058.78 680.59C1066.7 692.42 1068.77 706.89 1067.87 720.83C1066.48 739.32 1056.5 755.59 1048.27 771.72L1046.08 770.01C1050.58 765.85 1055.07 761.67 1059.52 757.47C1063.05 754.35 1067.82 748.94 1071.9 746.66C1073.53 745.69 1075.74 745.39 1077.57 746.23C1080.22 747.67 1080.72 750.84 1081.13 753.33C1082.05 762.67 1081.27 771.85 1081.15 781.16L1078.11 780.52C1083 767.16 1087.67 753.53 1090.51 739.59C1093.65 725.89 1089.36 711.54 1081.49 700.14C1077.52 694.45 1072.57 689.43 1066.71 685.72C1056.46 679.53 1045.27 675.57 1034.94 668.97C1020.2 659.91 1009.65 644.64 1005.89 627.79C1001.16 606.3 1004.51 584.13 1008.63 562.88C1010.52 553.45 1012.69 544.09 1014.95 534.76C1017.66 523.11 1021.08 511.67 1022.7 499.9C1023.09 496.49 1023.3 493.1 1022.47 489.83C1021.44 486.54 1019.55 483.63 1017.89 480.58C1013.11 472.37 1007.91 464.33 1002.38 456.59C999.67 452.96 996.97 448.93 993.55 446.04C989.81 443.79 985.15 443.07 980.85 443.63C971.31 444.78 965.55 453.36 959.39 459.9C953.07 467.06 946.92 474.4 940.87 481.79C940.21 482.6 939.7 481.96 939.09 481.46C938.48 480.96 937.76 480.61 938.41 479.79C947.61 468.64 956.66 457.45 966.86 447.15C973.87 440.01 985.58 437.98 994.49 442.75C996.02 443.6 997.29 444.87 998.31 446.02C1000.76 448.75 1002.94 451.69 1005.08 454.64C1010.7 462.49 1015.93 470.55 1020.81 478.89C1022.59 482.11 1024.66 485.35 1025.74 488.94C1026.71 492.65 1026.51 496.6 1026.08 500.29C1024.46 512.27 1021.01 523.9 1018.31 535.56C1016.07 544.86 1013.92 554.18 1012.06 563.55C1007.02 590.7 1002.06 621.51 1017.39 646.24C1029.55 666.52 1049.65 671.34 1068.78 682.46C1083.42 691.83 1093.12 708.24 1095.02 725.45C1096.04 735.27 1093.08 745.02 1090.49 754.35C1087.76 763.61 1084.54 772.66 1081.05 781.65L1077.55 790.67C1077.92 781.54 1078.57 771.7 1078.64 762.68C1078.54 759.83 1079.33 748.96 1075.67 748.51C1072.77 748.33 1070.32 751.37 1068.14 753.19C1061.66 759.25 1054.53 766.12 1047.99 772.05L1041.71 777.83L1045.8 770.34C1052.07 758.47 1058.68 746.52 1062.5 733.63C1066.82 717.39 1065.37 696.89 1055.14 683.1C1052.61 679.67 1049.44 676.67 1045.89 675.12C1042.59 673.53 1042.27 670.03 1045.51 671.66L1046.37 671.99V672.02Z" fill="#0E1016"/>
<path d="M356.7 532.55C364.18 473.86 377.64 415.86 398.09 360.3C420.06 303.5 451.46 250.78 487.25 201.68C492.08 195.14 496.95 188.7 502.12 182.33C503.2 181 503.42 183.37 504.75 184.44C506.1 185.54 508.01 184.98 506.87 186.3C501.77 192.4 496.79 198.78 491.93 205.18C448.48 262.97 411.35 326.6 390.32 396.09C375.75 442.22 366.08 489.81 361.56 537.97C361.41 539.48 356.5 534.16 356.71 532.55H356.7Z" fill="#0E1016"/>
<path d="M357.9 576.12C357.13 587.42 355.17 614.17 354.42 624.96C353.19 641.28 352.76 657.56 351.5 673.92C348.47 717.63 340.21 761.15 323.69 801.86C311.41 832.41 297 862.29 276.84 888.56C250.47 922.35 218.67 954.52 192.68 988.46C179.49 1005.59 167.12 1023.37 157.58 1042.86C157.34 1043.36 156.74 1043.57 156.24 1043.32C155.78 1043.1 155.57 1042.57 155.73 1042.09C159.3 1031.7 164.1 1021.74 169.53 1012.18C180.3 993.18 193.72 975.42 207.62 958.6C228.31 933.37 251.12 909.86 271.51 884.45C291.64 858.98 306.51 829.84 318.76 799.87C331.16 769.82 339.19 738.15 343.26 705.91C347.6 673.74 347.9 640.71 350.26 608.31L352.29 575.67C352.39 574.12 353.77 575.84 355.32 575.93C356.9 576.03 358.04 574.53 357.89 576.11L357.9 576.12Z" fill="#0E1016"/>
<path d="M339.82 568.03C337.92 582.56 336.3 597.12 335.47 611.75C333.86 645.84 330.29 682.43 310.15 711.33C300.13 725.21 285.11 734.69 276.77 749.77C270.82 760.07 266.36 771.51 263.84 783.11L260.35 782.09C264.05 774.45 268.15 767.18 273.69 760.69C278.01 755.85 283.27 751.53 289.61 749.57C297.07 747.25 304.47 749.38 307.82 756.85C311.76 765.15 310.48 774.14 308.65 782.69C298.76 827.7 272.69 867.15 244.12 902.49C239.75 907.83 234.63 913.94 230.09 919.11C218.26 933.01 201.31 940.62 185.01 947.56C170.15 953.67 154.36 959.11 142.44 970.19C136.56 975.51 131.69 981.96 127.38 988.8C123.18 995.53 119.4 1002.81 117.19 1010.4L113.3 1008.24C119.55 1002.37 125.99 996.92 132.98 991.97C136.57 989.49 140.23 987.08 144.34 985.45C153.14 981.66 165.56 982.72 169.33 992.95C172.14 1001.86 169.63 1011.12 167.11 1019.69C165.74 1023.9 164.22 1027.94 161.86 1032.14C160.68 1034.28 157.97 1035.06 155.83 1033.86C153.47 1032.56 152.84 1029.35 154.53 1027.26C156.82 1024.3 158.7 1020.64 160.25 1017.13C163.04 1010.34 168.08 996.55 161.52 990.8C157.23 987.3 151.16 987.89 145.99 989.76C142.23 991.12 138.82 993.37 135.51 995.62C126.45 1001.75 118.21 1009.65 110.64 1017.52C111.71 1013.27 113.1 1007.02 114.65 1003.15C120.07 989.35 127.9 976.52 138.73 966.18C148.01 957.42 159.65 951.6 171.35 946.9C189.72 939.09 209.74 932.02 224.11 917.84C229.64 911.83 235.02 905.61 240.19 899.29C265.8 867.67 288.77 832.64 300.46 793.41C302.58 785.7 304.66 777.85 305.2 769.86C305.3 763.92 304.48 756.21 298.66 753.51C294.28 752.07 289.65 753.3 285.55 755.73C275.2 761.95 268.89 772.95 263.69 783.62L258.52 796.1L260.2 782.6C262.41 767.71 267.78 753.23 276.08 740.66C284.38 728.44 297.01 719.35 305.81 708.07C326.83 680.77 330.02 644.88 331.56 611.55C332.39 596.83 333.8 582.15 335.51 567.52C335.65 566.33 336.76 567.23 337.95 567.37C339.15 567.51 339.98 566.85 339.82 568.05V568.03Z" fill="#0E1016"/>
<path d="M334.31 490.92C341.11 485.9 348.18 487.71 348.85 496.77C349.42 502.08 348.12 508.92 347.67 514.14C347.27 517.1 342.91 516.63 343.16 513.65C343.74 508.87 345.35 501.75 345.08 497.09C344.9 490.31 340.7 490.36 335.75 493.21C334.26 494.03 332.94 491.91 334.32 490.93L334.31 490.92Z" fill="#0E1016"/>
<path d="M497.03 182.91C489.52 187.07 481.24 189.2 473.29 192.22C449.52 201.24 429.57 215.49 416.36 237.5L414.02 235.46C417.91 232.41 421.87 229.54 425.97 226.8C432.13 222.72 438.67 218.73 445.87 216.54C451.99 214.62 462.39 212.86 464.4 221.19C465.77 228.1 460.66 234.48 457.54 239.38L449.63 251.65C444.41 259.87 439.17 268.09 434.09 276.37C431.6 280.58 428.74 284.9 425.34 288.52C420.28 294.07 414.11 298.48 407.7 302.31C401.65 305.81 395 309.81 389.68 314.29C382.28 320.35 375.93 327.64 370.91 335.79C365.95 343.91 361.58 352.74 358.81 361.8L355.92 360.4C358.58 356.87 361.37 353.56 364.34 350.33C370.4 343.86 377.17 337.68 385.9 334.88C389.37 333.87 393.96 333.32 396.98 336.39C399.63 339.69 398.38 344.05 397.6 347.47C395.84 353.82 393.24 359.83 390.95 365.93C383.53 385.29 376.16 404.71 370.38 424.61C368.35 429.74 364.5 433.71 361.02 437.79C355.18 444.28 349.01 450.37 342.8 456.38C336.66 462.19 330.78 468.4 326.72 475.83C326.27 476.67 325.22 476.98 324.39 476.52C323.57 476.07 323.26 475.05 323.69 474.23C325.74 470.26 328.28 466.59 331.05 463.16C339.46 453.3 349.4 444.74 357.82 434.96C360.93 431.21 364.58 427.48 366.36 422.96C369.69 411.37 373.67 399.94 377.86 388.67C380.88 380.56 384.02 372.5 387.2 364.47C388.78 360.45 390.43 356.44 391.93 352.46C393.24 348.78 394.92 344.53 394.83 340.73C394.75 336.85 389.75 337.49 386.9 338.28C372.67 343.02 362.3 357.57 353.44 369.06C358.8 347.19 369.96 326.08 387.42 311.56C394.73 305.19 403.88 300.68 411.81 295.22C419.76 289.8 426.05 282.71 430.71 274.32C435.77 265.96 440.92 257.68 446.15 249.44L454.05 237.11C456.42 233.44 459.57 229.23 460.31 224.95C462.2 214.6 445.31 220.26 440.33 222.58C429.35 227.74 419.13 235.51 409.48 242.88L413.63 235.91C422.45 221.33 434.43 208.58 449.05 199.78C459.81 193.02 471.52 187.92 483.49 183.76C487.24 182.27 491.05 180.61 494.49 178.57C496.06 177.68 495.51 179.49 496.05 180.75C496.48 181.76 498.35 182.07 497.03 182.91V182.91Z" fill="#0E1016"/>
<path d="M467.53 361.95C467.15 361.95 466.78 361.78 466.54 361.46C466.12 360.91 466.22 360.13 466.77 359.71C475.67 352.88 491.73 344.04 517.7 340.65C530.58 338.97 541.95 339.86 550.62 343.22C558.56 346.3 563.91 351.37 565.7 357.48C565.89 358.14 565.51 358.84 564.85 359.03C564.19 359.22 563.49 358.84 563.3 358.18C560.37 348.13 545.24 339.58 518.03 343.12C492.61 346.44 476.95 355.05 468.3 361.69C468.07 361.86 467.81 361.95 467.54 361.95H467.53Z" fill="#0E1016"/>
<path d="M455.46 446.85C455.08 446.85 454.71 446.68 454.47 446.36C454.05 445.81 454.16 445.03 454.7 444.61C454.91 444.45 476.47 428.15 505.18 421.86C533.01 415.76 555.16 420.33 559.03 432.97C559.23 433.63 558.86 434.33 558.2 434.53C557.54 434.73 556.84 434.36 556.64 433.7C553.71 424.13 535.27 417.82 505.72 424.3C477.55 430.47 456.43 446.44 456.22 446.6C455.99 446.77 455.73 446.86 455.46 446.86V446.85Z" fill="#0E1016"/>
<path d="M461.32 546.49C460.99 546.49 460.66 546.36 460.42 546.11C459.94 545.61 459.96 544.82 460.45 544.34C461.1 543.71 476.85 528.84 510.51 521.95C546.14 514.66 568.74 519.19 575.33 526.92C575.78 527.44 575.71 528.23 575.19 528.68C574.66 529.13 573.87 529.06 573.43 528.54C567.51 521.6 545.46 517.35 511.01 524.4C478.05 531.15 462.34 545.99 462.18 546.14C461.94 546.37 461.63 546.49 461.31 546.49H461.32Z" fill="#0E1016"/>
<path d="M491.16 665.06C490.64 665.06 490.15 664.73 489.97 664.2C489.78 663.63 490.04 663.14 490.13 662.98C491.69 660.07 507.85 643.09 546.93 632.56C589.6 621.06 611.66 621.97 617.97 624.33C618.62 624.57 618.95 625.29 618.7 625.94C618.46 626.59 617.73 626.92 617.09 626.67C611.47 624.57 589.72 623.61 547.58 634.97C509.65 645.2 493.77 661.6 492.37 664.08C492.28 664.49 491.98 664.84 491.55 664.99C491.42 665.03 491.29 665.05 491.16 665.05V665.06Z" fill="#0E1016"/>
<path d="M529.56 796.56C529.05 796.56 528.57 796.24 528.38 795.73C528.15 795.08 528.49 794.36 529.13 794.13C529.34 794.05 550.45 786.51 566.36 782.83C567.04 782.67 567.7 783.09 567.86 783.77C568.02 784.44 567.6 785.11 566.92 785.27C551.15 788.92 530.18 796.41 529.97 796.49C529.83 796.54 529.69 796.56 529.55 796.56H529.56Z" fill="#0E1016"/>
<path d="M701.99 887.03C707.21 911.57 710.87 936.49 711.32 961.61C711.35 986.74 708.48 1011.96 700.97 1035.98C700.8 1036.51 700.24 1036.8 699.71 1036.63C699.21 1036.47 698.93 1035.95 699.04 1035.45C700.39 1029.39 701.49 1023.29 702.44 1017.16C706.21 992.69 708.11 967.84 705.79 943.15C704.26 924.62 701.48 906.13 698.02 887.88C697.78 886.61 699.11 887.99 700.08 887.77C701.05 887.55 701.65 885.72 701.98 887.03H701.99Z" fill="#0E1016"/>
<path d="M598.21 954.87C597.65 954.87 597.14 954.49 597 953.93C596.83 953.26 597.23 952.58 597.9 952.41C598.57 952.24 664.69 935.45 705.99 935.45C706.52 935.45 707.04 935.45 707.56 935.45C708.25 935.45 708.8 936.02 708.8 936.71C708.8 937.4 708.23 937.95 707.55 937.95H707.54C707.03 937.95 706.51 937.95 705.99 937.95C665.01 937.95 599.2 954.66 598.53 954.83C598.43 954.86 598.32 954.87 598.22 954.87H598.21Z" fill="#0E1016"/>
<path d="M667.69 249.39C674.37 244.19 677.29 234.94 676.75 226.67C676.7 226.12 677.11 225.64 677.66 225.59C678.21 225.54 678.69 225.95 678.74 226.5C678.78 227.72 678.78 228.86 678.73 230.02C678.55 237.61 676.52 245.3 672.09 251.57C671.52 252.41 671.31 252.68 670.52 253.41C669.45 254.45 670.27 253.3 669.24 252.23C667.68 250.72 665.55 249.59 667.68 249.37L667.69 249.39Z" fill="#0E1016"/>
<path d="M671.67 236.19C671.67 236.19 668.92 241.33 666.61 244.87C665.75 246.2 664.68 246.3 664.36 247.08C663.83 248.39 664.79 248.83 664.79 248.83C664.79 248.83 667.49 252.78 668.64 251.83C671.85 249.18 672.56 244.63 672.59 242.17C672.63 238.97 671.66 236.19 671.66 236.19H671.67Z" fill="#0E1016"/>
<path d="M680.33 262.88C677.33 260.43 675.97 258.81 676.8 255.71C678.78 248.31 681.04 238.92 678.29 232.12L676.86 227.94C676.44 226.73 678.24 226.03 678.73 227.24L680.36 231.41C684.2 239.79 682 249.42 681.38 258.16C681.38 259.35 681.7 260.34 682.71 261.14C684.54 262.6 682.1 264.43 680.32 262.89L680.33 262.88Z" fill="#0E1016"/>
<path d="M277.7 520.62C271.91 553.39 267.14 586.36 264.03 619.5L261.22 618.8C269.87 598.75 278.88 578.87 288.9 559.46C292.28 553 295.74 546.57 299.64 540.35C300.83 538.51 303.66 540.24 302.48 542.15C287.42 566.86 275.66 593.54 263.91 619.96L260.37 628.12C263.09 591.88 267.56 555.76 273.42 519.91C273.94 517.12 278.11 517.76 277.69 520.64L277.7 520.62Z" fill="#0E1016"/>
<path d="M423.65 225.06C408.45 218.8 396.44 224.8 385.17 235.38C374.49 245.46 365.94 257.58 358.22 270.06C332.96 312.3 314.61 358.61 302.05 406.15C301.77 407.21 300.68 407.84 299.63 407.56C298.57 407.28 297.94 406.19 298.22 405.14C303.53 386.06 309.41 367.09 316.7 348.61C331.73 311.03 360.29 245.36 395.04 223.86C403.97 218.38 415.37 217.75 425.04 221.76C427.21 222.73 425.87 225.95 423.65 225.06V225.06Z" fill="#0E1016"/>
<path d="M447.79 249.51C442.87 241.24 436.94 233.09 428.81 227.86C426.82 226.55 428.73 223.53 430.77 224.79C435.1 227.74 438.75 231.47 441.91 235.47C445.05 239.47 447.73 243.77 450.15 248.17C450.51 248.83 450.27 249.65 449.62 250.01C448.97 250.37 448.16 250.14 447.79 249.51Z" fill="#0E1016"/>
<path d="M316.48 391.19C324.31 365.58 333.05 340.23 343.6 315.59C350.72 299.02 358.38 282.51 370.16 268.61C382.95 253.18 405.55 236.67 425.31 250.32C431.14 254.29 435.78 259.92 439.21 265.94C439.76 266.9 439.43 268.12 438.47 268.67C437.56 269.19 436.42 268.92 435.84 268.08C433.34 264.55 430.55 261.27 427.44 258.45C408.8 241.16 387.7 256.15 374.17 272C362.72 285.4 354.86 301.33 347.46 317.3C336.59 340.64 326.18 367.18 316.49 391.18L316.48 391.19Z" fill="#0E1016"/>
<path d="M708.68 958.41C728.49 955.79 747.63 946.21 760.45 930.71C762.74 927.83 764.89 924.75 766.76 921.6C767.45 920.33 768.35 921.15 769.59 921.92C770.88 922.71 771.91 923.22 770.95 924.39C768.97 926.81 767.11 929.3 765.01 931.6C758.88 938.56 751.86 944.86 743.94 949.73C733.39 956.27 721.55 960.49 709.36 962.72C706.47 963.17 705.75 958.89 708.68 958.42V958.41Z" fill="#0E1016"/>
<path d="M947.59 525.89C950.71 522.68 950.52 517.62 947.47 514.42C946.9 513.77 944.89 511.72 944.23 511.06C924.69 491.83 906.12 471.54 889.27 449.89C871.17 426.64 853.77 401.93 843.59 374.07C842.14 370.05 840.84 366.04 839.94 361.76H842.32C835.71 401.81 830.93 442.32 828.77 482.88C828.42 488.68 828.43 495.09 828.66 500.07C828.75 502.13 824.79 502.34 824.81 500.19C826.15 453.58 831.83 407.2 839.93 361.31L841.01 355.35C841.01 355.36 842.31 361.3 842.31 361.31C846.56 377.46 854.23 392.56 862.56 406.97C875.44 428.5 891.19 448.62 907.23 467.86C919.4 482.33 932.37 496.18 945.74 509.56L947.95 511.85L949.05 513.05C952.75 517 952.92 523.41 949 527.3C948.03 528.23 946.65 526.85 947.58 525.89H947.59Z" fill="#0E1016"/>
<path d="M590.49 817.26C587.92 833.69 585.16 851.23 582.14 867.62L582.08 867.96C582.06 868.07 582.09 867.93 582.08 867.89C582.06 867.71 581.92 867.56 581.74 867.54L581.9 867.56C583.07 867.68 593.49 868.88 594.92 869.04C609.18 870.71 688.57 879.74 704.54 881.56C705.22 881.64 705.71 882.25 705.63 882.93C705.55 883.61 704.94 884.1 704.26 884.02L616.55 874.1C609.26 873.29 590.7 871.21 583.66 870.41C583.15 870.35 581.94 870.22 581.42 870.15C580.16 870.03 579.22 868.68 579.53 867.47C582.81 851.01 586.76 833.6 590.5 817.26H590.49Z" fill="#0E1016"/>
<path d="M626.09 879.21C624.59 879.01 622.81 877.17 622.49 879.14C619.12 903.19 612.26 927.31 598.86 947.73C587.03 965.38 571.95 980.85 554.88 993.52C555.24 992.86 555.61 992.19 555.96 991.52C561.44 980.9 566.22 969.66 567.85 957.73C566.55 960.4 565.23 963.07 563.9 965.74C561.03 971.5 557.18 976.72 552.47 981.1C546.56 986.62 540.2 991.71 533.67 996.4C504.48 1017.6 468.84 1029.1 432.74 1029.47C432.22 1029.47 431.79 1029.88 431.75 1030.4C431.71 1030.95 432.13 1031.43 432.68 1031.47C444.9 1032.32 457.26 1031.79 469.47 1029.92C499.06 1025.03 528.27 1011.95 550.67 991.62C548.05 996.62 545.41 1001.6 542.75 1006.51C555.65 998.7 568.07 989.94 579.01 979.53C588.05 970.75 596.44 961.31 603.55 950.84C617.66 929.74 625.14 904.93 628.58 880C628.81 877.95 627.58 879.4 626.08 879.2L626.09 879.21Z" fill="#0E1016"/>
<path d="M683.19 613.34L695.73 618.04L693.12 631.17C690.9 638.76 682.94 643.12 675.35 640.9C667.76 638.68 663.4 630.72 665.62 623.13C667.82 615.61 675.65 611.25 683.2 613.35L683.19 613.34ZM696.76 612.86L684.82 608.38L684.72 608.35C674.34 605.39 663.54 611.41 660.58 621.79C657.62 632.17 663.64 642.97 674.02 645.93C684.39 648.88 695.2 642.87 698.16 632.5L698.19 632.4L700.68 619.9L712.62 624.38L712.72 624.41C723.1 627.37 733.9 621.35 736.86 610.97C739.82 600.59 733.8 589.79 723.42 586.83C713.05 583.88 702.24 589.89 699.28 600.26L699.25 600.36L696.76 612.86ZM701.7 614.71L704.31 601.58C706.53 593.99 714.49 589.63 722.08 591.85C729.67 594.07 734.03 602.03 731.81 609.62C729.61 617.14 721.78 621.5 714.23 619.4L701.7 614.7V614.71Z" fill="black"/>
<path d="M823.98 622.99L828.19 610.28L841.41 612.39C849.08 614.32 853.74 622.11 851.81 629.78C849.88 637.45 842.09 642.11 834.42 640.18C826.82 638.27 822.17 630.61 823.97 622.98L823.98 622.99ZM822.99 609.46L818.97 621.56L818.94 621.66C816.38 632.14 822.81 642.71 833.29 645.27C843.77 647.83 854.34 641.4 856.9 630.92C859.46 620.44 853.04 609.87 842.56 607.31L842.46 607.29L829.87 605.28L833.89 593.18L833.92 593.08C836.48 582.6 830.05 572.03 819.57 569.47C809.09 566.91 798.52 573.34 795.96 583.82C793.4 594.3 799.82 604.87 810.3 607.43L810.4 607.45L822.99 609.46ZM824.65 604.45L811.43 602.34C803.76 600.41 799.1 592.62 801.03 584.95C802.96 577.28 810.75 572.62 818.42 574.55C826.02 576.46 830.67 584.12 828.87 591.75L824.65 604.45V604.45Z" fill="black"/>
<path d="M650.09 767.54L663.41 766.19L666.89 779.12C668.27 786.91 663.06 794.34 655.27 795.72C647.48 797.1 640.05 791.89 638.67 784.1C637.31 776.38 642.4 769.01 650.09 767.54V767.54ZM662.04 761.1L649.36 762.38L649.25 762.4C638.64 764.35 631.62 774.54 633.57 785.15C635.52 795.76 645.71 802.78 656.32 800.83C666.93 798.88 673.95 788.7 672 778.09L671.98 777.99L668.67 765.68L681.35 764.4L681.46 764.38C692.07 762.43 699.09 752.24 697.14 741.63C695.19 731.02 685 724 674.39 725.95C663.78 727.9 656.76 738.08 658.71 748.69L658.73 748.79L662.04 761.1ZM667.29 760.57L663.81 747.65C662.43 739.86 667.64 732.43 675.43 731.05C683.22 729.67 690.65 734.88 692.03 742.67C693.39 750.39 688.3 757.76 680.61 759.23L667.29 760.57V760.57Z" fill="black"/>
<path d="M774.66 828.41L777.35 815.29L790.73 815.83C798.58 816.84 804.12 824.03 803.1 831.88C802.09 839.73 794.9 845.27 787.05 844.25C779.28 843.25 773.76 836.19 774.66 828.4V828.41ZM772.08 815.09L769.51 827.58L769.49 827.69C768.18 838.4 775.8 848.14 786.51 849.45C797.22 850.76 806.96 843.14 808.27 832.43C809.58 821.72 801.96 811.98 791.25 810.67H791.14L778.4 810.15L780.97 797.66L780.99 797.55C782.3 786.84 774.68 777.1 763.97 775.79C753.26 774.48 743.52 782.1 742.21 792.81C740.9 803.52 748.52 813.26 759.23 814.57H759.34L772.08 815.09ZM773.14 809.92L759.77 809.38C751.92 808.37 746.38 801.18 747.4 793.33C748.41 785.48 755.6 779.94 763.45 780.96C771.22 781.96 776.74 789.02 775.84 796.81L773.14 809.92V809.92Z" fill="black"/>
<path d="M787.92 711.27L784.58 698.3L796.84 692.92C804.34 690.39 812.47 694.41 815 701.91C817.53 709.41 813.51 717.54 806.01 720.07C798.59 722.58 790.53 718.66 787.92 711.27V711.27ZM779.75 700.43L782.92 712.78L782.95 712.88C786.48 723.08 797.6 728.49 807.8 724.96C818 721.43 823.41 710.31 819.88 700.11C816.35 689.91 805.23 684.51 795.04 688.03L794.94 688.07L783.27 693.2L780.1 680.85L780.07 680.75C776.54 670.55 765.42 665.14 755.22 668.67C745.02 672.2 739.61 683.32 743.14 693.52C746.67 703.72 757.79 709.12 767.98 705.6L768.08 705.56L779.75 700.43ZM778.44 695.32L766.19 700.7C758.69 703.23 750.56 699.21 748.03 691.71C745.5 684.21 749.52 676.08 757.02 673.55C764.44 671.04 772.5 674.96 775.11 682.35L778.44 695.31V695.32Z" fill="black"/>
<path d="M558.54 226.9C562.88 225.64 569.37 223.9 571.83 222.58C573.97 221.43 571.97 219.48 566.75 218.28C561.53 217.08 570.02 217.88 577.15 219.92C584.28 221.96 585.53 223.77 585.53 223.77L562.32 227.79L558.54 226.9V226.9Z" fill="#0E1016"/>
<path d="M624.42 238.21C624.42 238.21 628.55 244.98 630.13 262.5C630.91 271.15 627.12 256.56 625.63 251.91C624.14 247.26 620.04 237.59 620.04 237.59L624.41 238.21H624.42Z" fill="#0E1016"/>
<path d="M653.73 251C659.19 250.57 662.92 248.14 671.49 255.81C680.06 263.48 666.19 247.79 664.36 247.08C662.53 246.37 655.93 248.9 655.05 249.17C654.17 249.44 653.72 251 653.72 251H653.73Z" fill="#0E1016"/>
<path d="M462.61 534.24C463.12 536.37 465.48 539.98 469.78 537.46C474.08 534.94 467.37 540.41 465.46 541.76C463.55 543.11 461.62 544.4 461.62 544.4L462.61 534.24V534.24Z" fill="#0E1016"/>
<path d="M465.26 553.07C464.81 550.23 464.25 543.93 470.74 539.87C477.23 535.81 468.41 540.42 465.46 541.77C462.51 543.12 461.62 544.41 461.62 544.41L463.43 550.58L465.26 553.07V553.07Z" fill="#0E1016"/>
<path d="M458.38 451.18C458.41 448.94 459.16 444.22 464.27 441.26C469.38 438.3 462.75 441.55 460.32 442.59C457.89 443.63 456.52 444.75 456.52 444.75L457.16 450.95L458.38 451.19V451.18Z" fill="#0E1016"/>
<path d="M458.88 433.86C458.76 436.22 459.6 441.02 464.05 438.48C468.5 435.94 462.79 441.37 459.49 442.96C456.19 444.55 455.03 445.11 455.03 445.11L457.55 436.43L458.88 433.86Z" fill="#0E1016"/>
<path d="M469.45 366.82C470.21 363.93 471.59 358.71 477.34 355.77C483.09 352.83 469.52 359.16 469.09 359.48C468.66 359.8 468.5 364.4 468.5 364.4L469.45 366.82Z" fill="#0E1016"/>
<path d="M474.85 348.11C474.03 350.71 473.83 354.62 478.17 352.48C482.51 350.35 476.36 355.04 473.64 356.8C470.92 358.56 469.53 358.46 469.53 358.46L472.47 351.77L474.86 348.11H474.85Z" fill="#0E1016"/>
<path d="M490.49 653.53C491.04 655.23 493.71 658.29 498.56 654.81C503.42 651.33 491.76 662.01 491.55 662.22C491.34 662.43 490.49 653.53 490.49 653.53V653.53Z" fill="#0E1016"/>
<path d="M495.93 669.92C494.98 667.1 493.09 661.87 499.18 657.49C505.27 653.11 495.03 659.89 492.93 660.95C490.83 662.01 490.71 663.81 490.94 664.44C491.17 665.07 495.94 669.92 495.94 669.92H495.93Z" fill="#0E1016"/>
<path d="M530.6 787.37C531.01 789.29 532.79 792.81 537.64 791.22C542.49 789.63 532.42 794.22 530.85 794.72C529.28 795.23 529.89 791.04 529.89 791.04L530.6 787.36V787.37Z" fill="#0E1016"/>
<path d="M533.56 801.92C532.98 798.93 533.94 795.06 538.56 793.55C543.18 792.04 532.28 794.64 530.9 795.28C529.52 795.92 530.16 799.06 530.16 799.06L533.56 801.92Z" fill="#0E1016"/>
<path d="M462.77 230.77C467.46 222.37 470.13 218.32 468.48 214.66C466.56 210.41 456.16 213.52 445.87 216.54C435.58 219.56 452.63 215.94 456.84 216.87C461.05 217.8 463.27 219.17 463.27 222.98C463.27 226.79 462.78 230.76 462.78 230.76L462.77 230.77Z" fill="#0E1016"/>
<path d="M377.98 338.57C386.82 332.9 394.36 329.15 398.09 330.12C402.51 331.26 399.79 340.64 397.58 347.48C395.37 354.32 395.88 346.6 396.45 344.03C397.02 341.46 397.09 336.82 393.81 336.04C390.53 335.26 383.32 337.47 383.32 337.47L377.97 338.58L377.98 338.57Z" fill="#0E1016"/>
<path d="M332.23 490.25C334.67 486.11 340.65 478.95 345.39 479.53C350.59 480.17 350.59 486.67 348.85 503.23C347.16 519.25 346.95 503.94 346.87 501.09C346.79 498.24 346.71 491.11 343.31 490.08C339.91 489.05 336.03 490.79 336.03 490.79L332.24 490.25H332.23Z" fill="#0E1016"/>
<path d="M355.13 528.76C356.27 530.26 357.54 531.93 358.77 533.56C359.46 526.67 360.16 520.63 360.87 516.15C364.14 495.35 369.2 459.83 369.2 459.83C364.3 479.9 359.33 501.48 355.13 528.77V528.76Z" fill="#0E1016"/>
<path d="M316.69 805.25C312.98 813.82 310.38 819.27 310.38 819.27C310.38 819.27 313.02 814.18 316.69 805.25Z" fill="#0E1016"/>
<path d="M349.64 573.09C348.49 585.05 347.48 597.98 346.66 612.06C342.39 685.38 342.89 693.99 335.26 740.04C330.61 768.09 322.4 791.34 316.69 805.25C324.42 787.42 337.02 756.06 341.25 732.14C347.52 696.72 351.06 625.6 352.69 608.43C353.33 601.7 354.22 589.24 355.27 575.33L349.63 573.08L349.64 573.09Z" fill="#0E1016"/>
<path d="M629.15 238C623.94 240.21 617.4 240.51 610.49 238.46C603.58 236.41 592.48 230.33 583.43 225.39C574.39 220.45 572.36 217.19 577.93 220.15C583.5 223.11 598.47 228.58 605.69 231.93C612.91 235.27 617.27 236.41 621.74 236.87C626.21 237.33 629.14 238.01 629.14 238.01L629.15 238Z" fill="#0E1016"/>
<path d="M661.27 300.63C662.74 299.22 664.31 296.25 661.83 292.65C659.35 289.05 666.12 295.42 666.98 296.83C667.84 298.24 663.56 301.4 663.56 301.4L661.28 300.63H661.27Z" fill="#0E1016"/>
<path d="M662.35 288.2C664.24 290.11 666.92 292.73 669.7 293.53C672.48 294.33 674.51 294.43 676.44 294.48C678.37 294.53 671.41 297.3 669.16 297.59C666.91 297.88 663.92 292.03 663.92 292.03L662.35 288.2Z" fill="#0E1016"/>
<path d="M538.18 306.71C549.02 314.97 556.61 317.04 564.21 318.72C571.8 320.4 578.81 320.59 578.81 320.59L584.33 310.83C584.33 310.83 567.26 314.87 555.53 311.42C543.8 307.97 538.19 306.71 538.19 306.71H538.18Z" fill="#0E1016"/>
<path d="M489.73 186.33C491.84 185.45 493.42 185.49 494.86 186.33C496.3 187.17 497.26 188.4 495.5 190.7C493.74 192.99 499.13 189.49 500.57 187.05C502.01 184.6 500.07 182.59 500.07 182.59L494.11 181.93L489.73 186.33Z" fill="#0E1016"/>
<path d="M597.96 162.84C600.84 164.24 603.13 164.22 604.65 163.81C606.17 163.4 606.71 162.07 606.04 159.79C605.36 157.51 608.78 162.68 609.55 166.43C610.32 170.19 609.48 171.4 609.55 171.19C609.62 170.98 597.96 162.84 597.96 162.84V162.84Z" fill="#0E1016"/>
<path d="M198.66 292.78C207.5 284.7 216.43 279.9 225.64 277.76C234.85 275.62 242.29 277.26 247.71 284.45C253.13 291.64 257.08 299.75 260.29 314.33C263.49 328.91 258.18 290.76 256.04 281.05C253.9 271.34 251.88 265.41 245.19 263.39C238.5 261.37 236.99 260.74 226.14 267.8C215.29 274.86 211.76 277.01 206.34 282.18C200.92 287.35 198.66 292.79 198.66 292.79V292.78Z" fill="#0E1016"/>
<path d="M358.29 295.48C365.2 282.88 371.51 274.5 382.77 266.79C394.03 259.08 407.34 259.76 415.29 262.21C423.24 264.66 432.66 272.61 432.66 272.61L437.19 267.23C437.19 267.23 431.19 256.34 419.57 251.94C407.95 247.54 402.07 247.54 387.64 257.45C373.21 267.36 368.31 274.58 365.25 279.72C362.19 284.86 358.29 295.5 358.29 295.5V295.48Z" fill="#0E1016"/>
<path d="M277.18 523.58C277.18 523.58 284.25 532.15 288.84 536.33C293.43 540.51 298.59 544.93 298.59 544.93L300.72 542.14L277.32 522.78L277.18 523.57V523.58Z" fill="#0E1016"/>
<path d="M283.58 752.27C291.67 747.3 297.07 745.28 302.75 745.1C308.43 744.92 313.55 749.04 313.46 757.27C313.37 765.51 307.92 785.86 307.92 785.86L307.05 781.34C307.05 781.34 309.25 768.8 305.59 760.65C301.93 752.5 302.66 751.86 296.16 751.5C289.66 751.14 283.58 752.26 283.58 752.26V752.27Z" fill="#0E1016"/>
<path d="M138.58 988.32C151.33 980.28 158.34 976.67 167.23 978.29C178 980.25 177.51 996.2 168.68 1014.08C159.85 1031.96 166.95 1011.66 167.51 1006.4C168.08 1001.15 168.36 992.91 162.68 988.94C157 984.96 150.47 984.68 146.35 986.53C142.23 988.38 138.57 988.33 138.57 988.33L138.58 988.32Z" fill="#0E1016"/>
<path d="M541.13 996.16C541.13 996.16 535.65 1019.15 530.19 1031.87C524.73 1044.6 520.63 1036.67 520.63 1036.67C520.63 1036.67 528.89 1016.72 533.19 1002.31C535.75 993.72 541.14 996.16 541.14 996.16H541.13Z" fill="#0E1016"/>
<path d="M161.88 411.02C163.1 409.03 167.1 408.36 165.1 413.5C163.09 418.65 166.36 411.56 169.89 403.85C173.42 396.15 176.72 386.1 176.72 386.1L161.88 411.01V411.02Z" fill="#0E1016"/>
<path d="M216.07 383.78C217.18 380.12 221.2 377.18 221.02 383.78C220.84 390.38 222.84 376.66 223.01 368.8C223.18 360.95 223.01 356.37 223.01 356.37L216.06 383.78H216.07Z" fill="#0E1016"/>
<path d="M958.73 504.33C958.73 504.33 960.72 514.28 960.67 518.23C960.62 522.18 960.32 516.56 959 512.67C957.68 508.78 955.65 505.67 955.65 505.67L958.73 504.33Z" fill="#0E1016"/>
<path d="M380.03 704.74C399.67 716.2 417.35 732.33 426.95 753.29C434.12 768.64 438.08 785.47 439.99 802.29L434.58 794.34C429.59 787.06 424.45 779.79 418.89 772.93C415.32 768.5 411.4 764.36 406.27 762.01C398.1 758.07 390.72 762.34 393.49 771.83C394.93 777.34 398.45 782.14 401.4 787.03C404.55 792.1 407.55 797.37 410.17 802.78C416.44 815.33 420.75 828.99 421.03 843.13C417.32 838.16 413.36 832.88 409.48 828.07C403.97 821.59 398.54 814.17 390.28 811.24C387.33 810.24 383.91 809.83 381.04 811.24C380.07 811.72 379.23 812.48 378.54 813.31C380.63 808.88 386.51 808.18 390.84 809.25C399.99 811.46 406.33 819.05 412.2 825.81C415.1 829.25 417.81 832.79 420.36 836.52L417.25 837.6C416.71 831.93 415.24 826.12 413.47 820.65C409.76 809.56 404.18 799.09 397.9 789.19C393.89 782.66 388.91 776.01 388.7 767.95C388.57 764.43 390.15 760.49 393.18 758.44C403.69 751.51 416.09 762.08 422.29 770.1C428 777.08 433.2 784.36 438.32 791.77L434.22 793.31C433.66 788.99 432.79 784.54 431.81 780.2C423.98 745.09 408.77 725.65 380.04 704.69L380.03 704.74Z" fill="#0E1016"/>
<path d="M325.97 926.16C331.13 946.61 330.13 965.27 318.92 983.56C317.99 978.1 316.91 972.19 315.46 966.87C314.48 963.36 313.37 959.48 311.46 956.47C308.42 951.82 303.3 951.44 300.78 956.81C299.94 958.44 299.48 960.27 298.96 962.04C294.77 977 289.6 993.59 276.79 1003.55C284.27 994.29 288.7 983.53 292.13 972.33C293.58 967.66 294.77 962.88 296.2 958.14C296.53 957.23 296.93 955.96 297.41 955.11C299.53 950.55 304.37 947.51 309.45 949.79C313.76 951.64 315.95 956.04 317.4 960.06C319.44 965.78 320.72 971.63 321.74 977.6L318.18 976.99C326.54 960.8 327.68 944.05 325.96 926.17L325.97 926.16Z" fill="#0E1016"/>
<defs>
<linearGradient id="paint0_linear_121_754" x1="656.82" y1="655.74" x2="908.24" y2="717.18" gradientUnits="userSpaceOnUse">
<stop stop-color="#78E382"/>
<stop offset="1" stop-color="#3685DB"/>
</linearGradient>
<linearGradient id="paint1_linear_121_754" x1="884.03" y1="340.97" x2="946.58" y2="486.33" gradientUnits="userSpaceOnUse">
<stop stop-color="#78E382"/>
<stop offset="1" stop-color="#3685DB"/>
</linearGradient>
</defs>
</svg>

After

(image error) Size: 101 KiB

@ -0,0 +1,28 @@
{
"title": "Sign Up",
"og-title": "Replace .env files with 1 line of code. Sign Up for Infisical in 3 minutes.",
"og-description": "Infisical a simple end-to-end encrypted platform that enables teams to sync and manage API-keys and environemntal variables. Works with Node.js, Next.js, Gatsby, Nest.js...",
"signup": "Sign Up",
"already-have-account": "Have an account? Log in",
"forgot-password": "Forgot your password?",
"verify": "Verify",
"step1-start": "Let's get started",
"step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.",
"step1-submit": "Get Started",
"step2-message": "We've sent a verification code to",
"step2-code-error": "Oops. Your code is wrong. Tries left:",
"step2-resend-alert": "Don't see the code?",
"step2-resend-submit": "Resend",
"step2-resend-progress": "Resending...",
"step2-spam-alert": "Make sure to check your spam inbox.",
"step3-message": "Almost there!",
"step4-message": "Save your Emergency Kit",
"step4-description1": "If you get locked out of your account, your Emergency Kit is the only way to sign in.",
"step4-description2": "We recommend you download it and keep it somewhere safe.",
"step4-description3": "It contains your Secret Key which we cannot access or recover for you if you lose it.",
"step4-download": "Download PDF",
"step5-send-invites": "Send Invites",
"step5-invite-team": "Invite your team",
"step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.",
"step5-skip": "Skip"
}

@ -9,9 +9,9 @@
"step1-start": "Let's get started",
"step1-privacy": "By creating an account, you agree to our Terms and have read and acknowledged the Privacy Policy.",
"step1-submit": "Get Started",
"step2-message": "We've sent a verification email to",
"step2-message": "We've sent a verification code to",
"step2-code-error": "Oops. Your code is wrong. Please try again.",
"step2-resend-alert": "Don't see the email?",
"step2-resend-alert": "Don't see the code?",
"step2-resend-submit": "Resend",
"step2-resend-progress": "Resending...",
"step2-spam-alert": "Make sure to check your spam inbox.",

@ -0,0 +1,28 @@
{
"title": "S'inscrire",
"og-title": "Remplacez les fichiers .env par 1 ligne de code. Inscrivez-vous à Infisical en 3 minutes.",
"og-description": "Infisical, une plate-forme simple et chiffré de bout en bout qui permet aux équipes de synchroniser et de gérer des clefs API et des variables d'environnement. Fonctionne avec Node.js, Next.js, Gatsby, Nest.js ...",
"signup": "S'inscrire",
"already-have-account": "Déjà inscris? Se connecter",
"forgot-password": "Mot de passe oublié?",
"verify": "Vérifier",
"step1-start": "Bon, on commence!",
"step1-privacy": "En créant votre compte, vous acceptez nos conditions et avez lu et reconnu notre politique de confidentialité.",
"step1-submit": "C'est parti",
"step2-message": "Nous avons envoyé un email de vérification à",
"step2-code-error": "Oops. Votre code est faux. Essais restants:",
"step2-resend-alert": "Vous ne voyez pas le code?",
"step2-resend-submit": "Renvoyer",
"step2-resend-progress": "Envoie en cours...",
"step2-spam-alert": "Assurez-vous de vérifier vos spams.",
"step3-message": "Nous y sommes presque!",
"step4-message": "Enregistrez votre kit d'urgence",
"step4-description1": "Si vous n'arrivez plus à vous connecter à votre compte, votre kit d'urgence est le seul moyen d'y arriver.",
"step4-description2": "Nous vous recommandons de le télécharger et de le garder en sécurité.",
"step4-description3": "Il contient votre clef secrète que nous ne pouvons pas récupérer pour vous si vous la perdez.",
"step4-download": "Téléchargez le PDF",
"step5-send-invites": "Envoyer les invitations",
"step5-invite-team": "Invitez votre équipe",
"step5-subtitle": "Infisical a pour but d'être utilisé avec vos coéquipiers. Invitez-les à le tester.",
"step5-skip": "Passer"
}

@ -11,7 +11,7 @@
"step1-submit": "C'est parti",
"step2-message": "Nous avons envoyé un email de vérification à",
"step2-code-error": "Oops. Votre code est faux. Veuillez réessayer.",
"step2-resend-alert": "Vous ne voyez pas l'email?",
"step2-resend-alert": "Vous ne voyez pas le code?",
"step2-resend-submit": "Renvoyer",
"step2-resend-progress": "Envoie en cours...",
"step2-spam-alert": "Assurez-vous de vérifier vos spams.",

@ -0,0 +1,21 @@
{
"title": "회원가입",
"og-title": "한줄의 코드르 .env파일을 교체하세요. 3분이면 가입할 수 있어요.",
"og-description": "Infisical은 팀원과 .env파일을 공유하고 연동할 수 있는 심플한 end-to-end 암호화 플렛폼입니다. Node.js, Next.js, Gatsby, Nest.js 와 같은 다양한 플렛폼에서 작동해요.",
"signup": "회원가입",
"already-have-account": "이미 계정이 있나요? 로그인하기",
"forgot-password": "비밀번호를 잊으셨나요?",
"verify": "인증",
"step1-start": "시작하기",
"step1-privacy": "회원가입시 약관과 개인 정보 보호 정책을 읽고 동의한 것으로 간주합니다.",
"step1-submit": "시작하기",
"step2-message": "<email>{{email}}</email><wrapper>로 인증 메일을 전송하였습니다</wrapper><email>{{email}}</email>",
"step2-code-error": "코드가 잘못된 것 같아요. 남은 시도:",
"step2-spam-alert": "스팸함에 메일이 있지는 않은지 확인하세요",
"step3-message": "거의다 끝났어요!",
"step4-message": "긴급복구 키트 저장하기",
"step4-description1": "계정이 잠겼을 경우 비상 키트를 사용하여 로그인할 수 있어요.",
"step4-description2": "다운로드 후 안전한 곳에 보관하는 것을 추천합니다.",
"step4-description3": "분실시 접근하거나 복구할 수 없는 시크릿 키가 포함되어 있어요.",
"step4-download": "PDF 다운로드"
}

@ -4,5 +4,6 @@
"og-description": "Infisical é uma plataforma simples e criptografada de ponta a ponta que permite que as equipes sincronizem e gerenciem seus arquivos .env.",
"login": "Entrar",
"need-account": "Precisa de uma conta Infisical?",
"forgot-password": "Esqueceu sua senha?",
"create-account": "Criar uma conta"
}
}

@ -0,0 +1,21 @@
{
"title": "Inscrever-se",
"og-title": "Substitua os arquivos .env por 1 linha de código. Cadastre-se no Infisical em 3 minutos.",
"og-description": "Infisical é uma plataforma criptografada de ponta a ponta simples que permite que as equipes sincronizem e gerenciem chaves de API e variáveis ambientais. Funciona com Node.js, Next.js, Gatsby, Nest.js...",
"signup": "Inscrever-se",
"already-have-account": "Possui uma conta? Conecte-se",
"forgot-password": "Esqueceu sua senha?",
"verify": "Verificar",
"step1-start": "Vamos começar",
"step1-privacy": "Ao criar uma conta, você concorda com nossos Termos e leu e reconheceu a Política de Privacidade.",
"step1-submit": "Iniciar",
"step2-message": "<wrapper>Enviamos um e-mail de verificação para</wrapper><email>{{email}}</email>",
"step2-code-error": "Ops. Seu código está errado. Tentativas restantes:",
"step2-spam-alert": "Certifique-se de verificar sua caixa de entrada de spam.",
"step3-message": "Quase lá!",
"step4-message": "Guarde o seu Kit de Emergência",
"step4-description1": "Se sua conta for bloqueada, seu Kit de emergência é a única maneira de fazer login.",
"step4-description2": "Recomendamos que você faça o download e guarde-o em algum lugar seguro.",
"step4-description3": "Ele contém sua chave secreta que não podemos acessar ou recuperar para você se você a perder.",
"step4-download": "Baixar PDF"
}

@ -0,0 +1,28 @@
{
"title": "Kayıt olun",
"og-title": "Tek satır kodla .env dosyalarını değiştirin. 3 dakika içerinde Infisical'a kayıt olun.",
"og-description": "Infisical takımların API anahtarlarını ve ortam değişkenlerini yönetmelerini ve senkronize etmelerini sağlayan basit, uçtan uca şifrelenmiş bir platformdur. Node.js, Next.js, Gatsby, Nest.js ve daha fazlası ile çalışır.",
"signup": "Kayıt ol",
"already-have-account": "Hesabın var mı? Giriş yap",
"forgot-password": "Şifreni mi unuttun?",
"verify": "Doğrula",
"step1-start": "Hadi başlayalım",
"step1-privacy": "Hesap oluşturarak, Şartlarımızı ve Gizlilik Politikasını okuyup kabul etmiş olursunuz.",
"step1-submit": "Başla",
"step2-message": "Şu adrese bir doğrulama maili yolladık",
"step2-code-error": "Tüh. Kodun hatalı. Kalan denemeler:",
"step2-resend-alert": "Kodu ulaşamadı mı?",
"step2-resend-submit": "Tekrar Yolla",
"step2-resend-progress": "Tekrar yollanıyor...",
"step2-spam-alert": "Spam kutunuzu kontrol ettiğinizden emin olun.",
"step3-message": "Çok az kaldı!",
"step4-message": "Acil Durum Kitinizi kayıt edin",
"step4-description1": "Eğer hesabınıza erişemezseniz, Acil Durum Kitiniz giriş yapmanın tek yoludur.",
"step4-description2": "Bunu indirmenizi ve güvenli bir ortamda saklamanızı öneriyoruz.",
"step4-description3": "Kaybetmeniz durumunda bizim dahi erişemeyeceğimiz veya kurtaramayacağımız Gizli Anahtarınızı barındırır.",
"step4-download": "PDF'yi indir",
"step5-send-invites": "Davetleri yolla",
"step5-invite-team": "Takımını davet et",
"step5-subtitle": "Infisical takım arkadaşlarınız ile kullanılmak üzere yapılmıştır. Birlikte test etmek için onları davet edin.",
"step5-skip": "Atla"
}

@ -11,7 +11,7 @@
"step1-submit": "Başla",
"step2-message": "Şu adrese bir doğrulama maili yolladık",
"step2-code-error": "Tüh. Kodun hatalı. Lütfen tekrar dene.",
"step2-resend-alert": "Mail ulaşamadı mı?",
"step2-resend-alert": "Kodu ulaşamadı mı?",
"step2-resend-submit": "Tekrar Yolla",
"step2-resend-progress": "Tekrar yollanıyor...",
"step2-spam-alert": "Spam kutunuzu kontrol ettiğinizden emin olun.",

@ -3,8 +3,8 @@ import { Switch } from '@headlessui/react';
interface ToggleProps {
enabled: boolean;
setEnabled: (value: boolean) => void;
addOverride: (value: string | undefined, pos: number) => void;
pos: number;
addOverride: (value: string | undefined, id: string) => void;
id: string;
}
/**
@ -13,18 +13,18 @@ interface ToggleProps {
* @param {boolean} obj.enabled - whether the toggle is turned on or off
* @param {function} obj.setEnabled - change the state of the toggle
* @param {function} obj.addOverride - a function that adds an override to a certain secret
* @param {number} obj.pos - position of a certain secret
* @param {number} obj.id - id of a certain secret
* @returns
*/
const Toggle = ({ enabled, setEnabled, addOverride, pos }: ToggleProps): JSX.Element => {
const Toggle = ({ enabled, setEnabled, addOverride, id }: ToggleProps): JSX.Element => {
return (
<Switch
checked={enabled}
onChange={() => {
if (enabled === false) {
addOverride('', pos);
addOverride('', id);
} else {
addOverride(undefined, pos);
addOverride(undefined, id);
}
setEnabled(!enabled);
}}

@ -1,7 +1,5 @@
import { Fragment } from 'react';
import { useRouter } from 'next/router';
import { Dialog, Transition } from '@headlessui/react';
import { plans } from 'public/data/frequentConstants';
import Button from '../buttons/Button';
import InputField from '../InputField';
@ -12,7 +10,6 @@ type Props = {
submitModal: (email: string) => void;
email: string;
setEmail: (email: string) => void;
currentPlan: string;
orgName: string;
};
@ -22,13 +19,11 @@ const AddUserDialog = ({
submitModal,
email,
setEmail,
currentPlan,
orgName,
}: Props) => {
const submit = () => {
submitModal(email);
};
const router = useRouter();
return (
<div className='z-50'>
@ -81,28 +76,6 @@ const AddUserDialog = ({
isRequired
/>
</div>
{currentPlan === plans.starter && (
<div className='flex flex-row'>
<button
type='button'
className='inline-flex justify-center rounded-md py-1 text-sm text-gray-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
onClick={() =>
router.push(`/settings/billing/${ router.query.id}`)
}
>
You can add up to 5 members on the Starter plan.
</button>
<button
type='button'
className='ml-1 inline-flex justify-center rounded-md py-1 underline underline-offset-2 text-sm text-gray-500 hover:text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2'
onClick={() =>
router.push(`/settings/billing/${ router.query.id}`)
}
>
Upgrade now.
</button>
</div>
)}
<div className='mt-4 max-w-max'>
<Button
onButtonPressed={submit}

@ -45,19 +45,19 @@ export const DeleteEnvVar = ({ isOpen, onClose, onSubmit }: Props) => {
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-grey border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-gray-400">
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker border border-mineshaft-600 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-bunker-200">
{t('dashboard:sidebar.delete-key-dialog.title')}
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
<p className="text-sm text-bunker-300">
{t('dashboard:sidebar.delete-key-dialog.confirm-delete-message')}
</p>
</div>
<div className="mt-6 flex justify-start">
<button
type="button"
className="inline-flex justify-center rounded-md border border-transparent bg-red-700 hover:bg-red-600 px-4 py-2 text-sm font-medium text-bunker-200 hover:text-white text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
className="inline-flex justify-center rounded-md border border-transparent bg-red-500 opacity-80 hover:opacity-100 px-4 py-2 text-sm font-medium text-bunker-100 text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
onClick={onSubmit}
>
Delete

@ -30,7 +30,7 @@ type Props = {
type EnvironmentProps = {
name: string;
slug: string;
}
};
/**
* This is the component that shows the users of a certin project
@ -91,26 +91,38 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
});
};
const handlePermissionUpdate = (index: number, val: string, membershipId: string, slug: string ) => {
let denials: { ability: string; environmentSlug: string; }[];
if (val === "Read Only") {
denials = [{
ability: "write",
environmentSlug: slug
}];
} else if (val === "No Access") {
denials = [{
ability: "write",
environmentSlug: slug
}, {
ability: "read",
environmentSlug: slug
}];
} else if (val === "Add Only") {
denials = [{
ability: "read",
environmentSlug: slug
}];
const handlePermissionUpdate = (
index: number,
val: string,
membershipId: string,
slug: string
) => {
let denials: { ability: string; environmentSlug: string }[];
if (val === 'Read Only') {
denials = [
{
ability: 'write',
environmentSlug: slug
}
];
} else if (val === 'No Access') {
denials = [
{
ability: 'write',
environmentSlug: slug
},
{
ability: 'read',
environmentSlug: slug
}
];
} else if (val === 'Add Only') {
denials = [
{
ability: 'read',
environmentSlug: slug
}
];
} else {
denials = [];
}
@ -118,8 +130,12 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
if (currentPlan !== plans.professional && host === 'https://app.infisical.com') {
setIsUpgradeModalOpen(true);
} else {
const allDenials = userData[index].deniedPermissions.filter((perm: { ability: string; environmentSlug: string; }) => perm.environmentSlug !== slug).concat(denials);
updateUserProjectPermission({ membershipId, denials: allDenials});
const allDenials = userData[index].deniedPermissions
.filter(
(perm: { ability: string; environmentSlug: string }) => perm.environmentSlug !== slug
)
.concat(denials);
updateUserProjectPermission({ membershipId, denials: allDenials });
changeData([
...userData.slice(0, index),
...[
@ -156,7 +172,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
orgId
});
if (subscriptions) {
setCurrentPlan(subscriptions.data[0].plan.product)
setCurrentPlan(subscriptions.data[0].plan.product);
}
})();
}, [userData, myUser]);
@ -186,25 +202,28 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
const closeUpgradeModal = () => {
setIsUpgradeModalOpen(false);
}
};
return (
<div className="table-container bg-bunker rounded-md mb-6 border border-mineshaft-700 relative mt-1 min-w-max">
<div className="absolute rounded-t-md w-full h-[3.1rem] bg-white/5" />
<div className="table-container relative mb-6 mt-1 min-w-max rounded-md border border-mineshaft-700 bg-bunker">
<div className="absolute h-[3.1rem] w-full rounded-t-md bg-white/5" />
<UpgradePlanModal
isOpen={isUpgradeModalOpen}
onClose={closeUpgradeModal}
text="You can change user permissions if you switch to Infisical's Professional plan."
/>
<table className="w-full my-0.5">
<thead className="text-gray-400 text-xs font-light">
<table className="my-0.5 w-full">
<thead className="text-xs font-light text-gray-400">
<tr>
<th className="text-left pl-4 py-3.5">NAME</th>
<th className="text-left pl-4 py-3.5">EMAIL</th>
<th className="text-left pl-6 pr-10 py-3.5">ROLE</th>
{workspaceEnvs.map(env => (
<th key={guidGenerator()} className="text-left pl-2 py-1 max-w-min break-normal">
<span>{env.slug.toUpperCase()}<br/></span>
<th className="py-3.5 pl-4 text-left">NAME</th>
<th className="py-3.5 pl-4 text-left">EMAIL</th>
<th className="py-3.5 pl-6 pr-10 text-left">ROLE</th>
{workspaceEnvs.map((env) => (
<th key={guidGenerator()} className="max-w-min break-normal py-1 pl-2 text-left">
<span>
{env.slug.toUpperCase()}
<br />
</span>
{/* <span>PERMISSION</span> */}
</th>
))}
@ -227,28 +246,28 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
)
.map((row, index) => (
<tr key={guidGenerator()} className="bg-bunker-600 text-sm hover:bg-bunker-500">
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
<td className="border-t border-mineshaft-700 py-2 pl-4 text-gray-300">
{row.firstName} {row.lastName}
</td>
<td className="pl-4 py-2 border-mineshaft-700 border-t text-gray-300">
<td className="border-t border-mineshaft-700 py-2 pl-4 text-gray-300">
{row.email}
</td>
<td className="pl-6 pr-10 py-2 border-mineshaft-700 border-t text-gray-300">
<div className="justify-start h-full flex flex-row items-center">
<Select
<td className="border-t border-mineshaft-700 py-2 pl-6 pr-10 text-gray-300">
<div className="flex h-full flex-row items-center justify-start">
<Select
className="w-36 bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-700"
// open={isOpen}
onValueChange={(e) => handleRoleUpdate(index, e)}
value={row.role}
disabled={myRole !== 'admin' || myUser === row.email}
isDisabled={myRole !== 'admin' || myUser === row.email}
// onOpenChange={(open) => setIsOpen(open)}
>
<SelectItem value="admin">Admin</SelectItem>
<SelectItem value="member">Member</SelectItem>
</Select>
{row.status === 'completed' && myUser !== row.email && (
<div className="border border-mineshaft-700 rounded-md bg-white/5 hover:bg-primary text-white hover:text-black duration-200">
<div className="rounded-md border border-mineshaft-700 bg-white/5 text-white duration-200 hover:bg-primary hover:text-black">
<Button
onButtonPressed={() => grantAccess(row.userId, row.publicKey)}
color="mineshaft"
@ -259,43 +278,106 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
)}
</div>
</td>
{workspaceEnvs.map((env) => <td key={guidGenerator()} className="pl-2 py-2 border-mineshaft-700 border-t text-gray-300">
<Select
className="w-16 bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-700"
position="item-aligned"
// open={isOpen}
onValueChange={(val) => handlePermissionUpdate(index, val, row.membershipId, env.slug)}
value={
// eslint-disable-next-line no-nested-ternary
(row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read"))
? "No Access"
// eslint-disable-next-line no-nested-ternary
: (row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && !row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read") ? "Read Only"
: !row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read") ? "Add Only" : "Read & Write")
}
icon={
// eslint-disable-next-line no-nested-ternary
(row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read"))
? faEyeSlash
// eslint-disable-next-line no-nested-ternary
: (row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && !row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read") ? faEye
: !row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("write") && row.deniedPermissions.filter((perm: any) => perm.environmentSlug === env.slug).map((perm: {ability: string}) => perm.ability).includes("read") ? faPlus : faPenToSquare)
}
disabled={myRole !== 'admin'}
// onOpenChange={(open) => setIsOpen(open)}
{workspaceEnvs.map((env) => (
<td
key={guidGenerator()}
className="border-t border-mineshaft-700 py-2 pl-2 text-gray-300"
>
<SelectItem value="No Access" customIcon={faEyeSlash}>No Access</SelectItem>
<SelectItem value="Read Only" customIcon={faEye}>Read Only</SelectItem>
<SelectItem value="Add Only" customIcon={faPlus}>Add Only</SelectItem>
<SelectItem value="Read & Write" customIcon={faPenToSquare}>Read & Write</SelectItem>
</Select>
</td>)}
<td className="flex flex-row justify-end pl-8 pr-8 py-2 border-t border-0.5 border-mineshaft-700">
<Select
className="w-16 bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-700"
position="item-aligned"
// open={isOpen}
onValueChange={(val) =>
handlePermissionUpdate(index, val, row.membershipId, env.slug)
}
value={
// eslint-disable-next-line no-nested-ternary
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('write') &&
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('read')
? 'No Access'
: // eslint-disable-next-line no-nested-ternary
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('write') &&
!row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('read')
? 'Read Only'
: !row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('write') &&
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('read')
? 'Add Only'
: 'Read & Write'
}
icon={
// eslint-disable-next-line no-nested-ternary
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('write') &&
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('read')
? faEyeSlash
: // eslint-disable-next-line no-nested-ternary
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('write') &&
!row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('read')
? faEye
: !row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('write') &&
row.deniedPermissions
.filter((perm: any) => perm.environmentSlug === env.slug)
.map((perm: { ability: string }) => perm.ability)
.includes('read')
? faPlus
: faPenToSquare
}
isDisabled={myRole !== 'admin'}
// onOpenChange={(open) => setIsOpen(open)}
>
<SelectItem value="No Access" customIcon={faEyeSlash}>
No Access
</SelectItem>
<SelectItem value="Read Only" customIcon={faEye}>
Read Only
</SelectItem>
<SelectItem value="Add Only" customIcon={faPlus}>
Add Only
</SelectItem>
<SelectItem value="Read & Write" customIcon={faPenToSquare}>
Read & Write
</SelectItem>
</Select>
</td>
))}
<td className="border-0.5 flex flex-row justify-end border-t border-mineshaft-700 py-2 pl-8 pr-8">
{myUser !== row.email &&
// row.role !== "admin" &&
myRole !== 'member' ? (
<div className="opacity-50 hover:opacity-100 flex items-center mt-0.5">
<div className="mt-0.5 flex items-center opacity-50 hover:opacity-100">
<Button
onButtonPressed={() => handleDelete(row.membershipId, index)}
color="red"
@ -304,7 +386,7 @@ const ProjectUsersTable = ({ userData, changeData, myUser, filter }: Props) => {
/>
</div>
) : (
<div className="w-9 h-9" />
<div className="h-9 w-9" />
)}
</td>
</tr>

@ -36,7 +36,7 @@ const Notification = ({ notification, clearNotification }: NotificationProps) =>
return (
<div
className="relative w-full flex items-center justify-between px-6 py-6 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3"
className="relative w-full flex items-center justify-between px-6 py-4 rounded-md border border-bunker-500 pointer-events-auto bg-mineshaft-700 mb-3 right-3"
role="alert"
>
{notification.type === 'error' && (

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