mirror of https://github.com/boxyhq/jackson.git
Merge branch 'main' into feature/issue-61/jackson-docker-compose
This commit is contained in:
commit
680dec30ca
|
@ -18,6 +18,8 @@ DB_CLEANUP_LIMIT=1000
|
|||
DB_PAGE_LIMIT=50
|
||||
# You can use openssl to generate a random 32 character key: openssl rand -base64 24
|
||||
DB_ENCRYPTION_KEY=
|
||||
# Uncomment below if you wish to run DB migrations manually.
|
||||
#DB_MANUAL_MIGRATION=true
|
||||
|
||||
# Admin Portal settings
|
||||
# SMTP details for Magic Links
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
dist
|
||||
npm/dist
|
||||
npm/migration
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Report any issues with the platform
|
||||
title: ""
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ""
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
Found a bug? Please fill out the sections below. 👍
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest a feature or idea
|
||||
title: ""
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ""
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
> Please check if your Feature Request has not been already raised in the [Discussions Tab](https://github.com/boxyhq/jackson/discussions), as we would like to reduce duplicates. If it has been already raised, simply upvote it 🔼.
|
||||
|
@ -40,4 +40,4 @@ assignees: ""
|
|||
You might want to link to related issues here, if you haven't already.
|
||||
-->
|
||||
|
||||
(Write your answer here.)
|
||||
(Write your answer here.)
|
||||
|
|
|
@ -139,7 +139,8 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
||||
- run: npm run custom-install
|
||||
- run: npm run lint
|
||||
- run: npm run check-lint
|
||||
- run: npm run check-format
|
||||
- run: npm run build
|
||||
- run: npm run test
|
||||
working-directory: ./npm
|
||||
|
|
|
@ -5,6 +5,11 @@ public
|
|||
**/**/.next
|
||||
**/**/public
|
||||
npm/migration/**
|
||||
npm/dist/**
|
||||
npm/.nyc_output/**
|
||||
.vscode/**
|
||||
|
||||
npm/package-lock.json
|
||||
|
||||
*.lock
|
||||
*.log
|
||||
|
|
|
@ -3,9 +3,9 @@ module.exports = {
|
|||
bracketSameLine: true,
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
trailingComma: "es5",
|
||||
trailingComma: 'es5',
|
||||
semi: true,
|
||||
printWidth: 110,
|
||||
arrowParens: "always",
|
||||
importOrderSeparation: true,
|
||||
arrowParens: 'always',
|
||||
// importOrderSeparation: true,
|
||||
};
|
||||
|
|
|
@ -20,9 +20,7 @@
|
|||
"tagAnnotation": "Release ${version}",
|
||||
"tagArgs": [],
|
||||
"push": true,
|
||||
"pushArgs": [
|
||||
"--follow-tags"
|
||||
],
|
||||
"pushArgs": ["--follow-tags"],
|
||||
"pushRepo": ""
|
||||
},
|
||||
"npm": {
|
||||
|
@ -48,4 +46,4 @@
|
|||
"pr": ":rocket: _This pull request is included in v${version}. See [${releaseName}](${releaseUrl}) for release notes._"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
13
Dockerfile
13
Dockerfile
|
@ -1,4 +1,4 @@
|
|||
ARG NODEJS_IMAGE=node:18.18.0-alpine3.18
|
||||
ARG NODEJS_IMAGE=node:18.18.2-alpine3.18
|
||||
FROM --platform=$BUILDPLATFORM $NODEJS_IMAGE AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
|
@ -10,7 +10,7 @@ WORKDIR /app
|
|||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json package-lock.json ./
|
||||
COPY npm npm
|
||||
COPY prebuild.ts prebuild.ts
|
||||
COPY migrate.sh prebuild.ts ./
|
||||
RUN npm run custom-install
|
||||
|
||||
|
||||
|
@ -31,7 +31,6 @@ ENV NEXT_TELEMETRY_DISABLED 1
|
|||
|
||||
RUN npm run build
|
||||
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM $NODEJS_IMAGE AS runner
|
||||
WORKDIR /app
|
||||
|
@ -53,8 +52,12 @@ COPY --from=builder /app/public ./public
|
|||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
|
||||
# Support for DB migration
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/migrate.sh ./migrate.sh
|
||||
COPY npm npm
|
||||
RUN chmod +x migrate.sh
|
||||
# mongodb peer dependency would be automatically installed for migrate-mongo
|
||||
RUN npm install -g ts-node migrate-mongo typeorm reflect-metadata mssql mysql2 pg
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 5225
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
<h3 align="center">
|
||||
|
||||
[⬆️ Take a look at our Issues ⬆️](https://github.com/boxyhq/jackson/issues)
|
||||
|
||||
</h3>
|
||||
|
||||
___
|
||||
---
|
||||
|
||||
<h3 align="center" >
|
||||
<a href="https://boxyhq.com/docs/jackson/overview" rel="dofollow"><strong>· Explore the docs »</strong></a>
|
||||
|
@ -27,8 +28,6 @@ ___
|
|||
<a href="https://boxyhq.com/saas-registration" rel="dofollow"><strong>· SaaS Early Access »</strong></a>
|
||||
</h3>
|
||||
|
||||
|
||||
|
||||
# ⭐️ SAML Jackson: Enterprise SSO made simple
|
||||
|
||||
<p>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useRouter } from 'next/router';
|
||||
|
||||
import { CreateSAMLConnection as CreateSAML, CreateOIDCConnection as CreateOIDC } from '@boxyhq/react-ui/sso';
|
||||
|
||||
import { errorToast } from '@components/Toaster';
|
||||
|
@ -26,7 +27,7 @@ const CreateSSOConnection = ({ setupLinkToken, idpType }: CreateSSOConnectionPro
|
|||
save: `/api/setup/${setupLinkToken}/sso-connection`,
|
||||
};
|
||||
|
||||
|
||||
const _CSS = { input: 'input input-bordered', button: { ctoa: 'btn btn-primary' } };
|
||||
|
||||
return idpType === 'saml' ? (
|
||||
<CreateSAML
|
||||
|
@ -34,6 +35,8 @@ const CreateSSOConnection = ({ setupLinkToken, idpType }: CreateSSOConnectionPro
|
|||
urls={urls}
|
||||
successCallback={onSuccess}
|
||||
errorCallback={onError}
|
||||
classNames={_CSS}
|
||||
displayHeader={false}
|
||||
/>
|
||||
) : (
|
||||
<CreateOIDC
|
||||
|
@ -41,6 +44,8 @@ const CreateSSOConnection = ({ setupLinkToken, idpType }: CreateSSOConnectionPro
|
|||
urls={urls}
|
||||
successCallback={onSuccess}
|
||||
errorCallback={onError}
|
||||
classNames={_CSS}
|
||||
displayHeader={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -34,12 +34,16 @@ const handleGET = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
idp_hint: string;
|
||||
};
|
||||
|
||||
const { redirectUrl } = await samlFederatedController.sso.getAuthorizeUrl({
|
||||
const { redirect_url, authorize_form } = await samlFederatedController.sso.getAuthorizeUrl({
|
||||
request: SAMLRequest,
|
||||
relayState: RelayState,
|
||||
idp_hint,
|
||||
});
|
||||
|
||||
res.redirect(302, redirectUrl);
|
||||
return;
|
||||
if (redirect_url) {
|
||||
res.redirect(302, redirect_url);
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.send(authorize_form);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: jackson
|
||||
image: boxyhq/jackson:tagwillbereplaced
|
||||
image: boxyhq/jackson-local
|
||||
imagePullPolicy: IfNotPresent
|
||||
startupProbe:
|
||||
httpGet:
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: jackson-internal
|
||||
namespace: '{{repl ConfigOption "namespace"}}'
|
||||
labels:
|
||||
app: jackson-internal
|
||||
tier: jackson-internal
|
||||
spec:
|
||||
ports:
|
||||
- name: original
|
||||
port: 5225
|
||||
targetPort: 5225
|
||||
selector:
|
||||
app: jackson
|
||||
tier: jackson
|
|
@ -0,0 +1,4 @@
|
|||
resources:
|
||||
- ./jackson-internal-service.yaml
|
||||
|
||||
namespace: default
|
|
@ -1,3 +1,6 @@
|
|||
bases:
|
||||
- ./internal
|
||||
|
||||
resources:
|
||||
- ./jackson-service.yaml
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
*-secrets.yaml
|
||||
secrets.yaml
|
||||
**/*-secrets.yaml
|
||||
**/secrets.yaml
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jackson
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: jackson
|
||||
image: boxyhq/jackson:tagwillbereplaced
|
||||
imagePullPolicy: IfNotPresent
|
|
@ -4,7 +4,15 @@ bases:
|
|||
|
||||
resources:
|
||||
- ./secrets.yaml
|
||||
- ./mocksaml-secrets.yaml
|
||||
- ./mocksaml-deployment.yaml
|
||||
- ./migratepg-job.yaml
|
||||
|
||||
patches:
|
||||
- ./jackson-deployment.yaml
|
||||
|
||||
images:
|
||||
- name: boxyhq/jackson
|
||||
newTag: 1.13.0
|
||||
newTag: 1.14.0
|
||||
- name: boxyhq/mock-saml
|
||||
newTag: 1.1.7
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: jackson-migrate-pg
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: 'OnFailure'
|
||||
containers:
|
||||
- name: db
|
||||
image: boxyhq/jackson:tagwillbereplaced
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- migrate.sh
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: jackson
|
|
@ -0,0 +1,44 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mocksaml
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: mocksaml
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mocksaml
|
||||
tier: mocksaml
|
||||
spec:
|
||||
containers:
|
||||
- name: mocksaml
|
||||
image: boxyhq/mock-saml:tagwillbereplaced
|
||||
imagePullPolicy: IfNotPresent
|
||||
startupProbe:
|
||||
httpGet:
|
||||
port: 4000
|
||||
path: /api/health
|
||||
periodSeconds: 10
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 4000
|
||||
path: /api/health
|
||||
periodSeconds: 30
|
||||
failureThreshold: 5
|
||||
successThreshold: 2
|
||||
ports:
|
||||
- containerPort: 4000
|
||||
name: http
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: mocksaml
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
limits:
|
||||
cpu: 500m
|
|
@ -11,13 +11,11 @@ metadata:
|
|||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:us-west-1:511214097407:certificate/3cd477cb-f54b-4701-89f1-9416216cb1c9'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 5225
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 5225
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
---
|
||||
bases:
|
||||
- ../../../base/services/internal
|
||||
- ./jackson-service.yaml
|
||||
- ./mocksaml-service.yaml
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mocksaml
|
||||
labels:
|
||||
app: mocksaml
|
||||
tier: mocksaml
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:us-west-1:511214097407:certificate/10249968-7517-424c-a7c7-cd66b4310752'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 4000
|
||||
selector:
|
||||
app: mocksaml
|
||||
tier: mocksaml
|
|
@ -6,6 +6,7 @@ bases:
|
|||
|
||||
resources:
|
||||
- ./secrets.yaml
|
||||
- ./migratepg-job.yaml
|
||||
|
||||
commonLabels:
|
||||
jacksondev: '1'
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: jackson-migrate-pg
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: 'OnFailure'
|
||||
containers:
|
||||
- name: db
|
||||
image: boxyhq/jackson-local
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- migrate.sh
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: jackson
|
|
@ -34,3 +34,4 @@ stringData:
|
|||
RETRACED_EXTERNAL_URL: 'http://localhost:3000/auditlog'
|
||||
RETRACED_ADMIN_ROOT_TOKEN: 'dev'
|
||||
BOXYHQ_LICENSE_KEY: ''
|
||||
DB_MANUAL_MIGRATION: 'true'
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
**/*-secrets.yaml
|
||||
**/secrets.yaml
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: jackson
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: jackson
|
||||
image: boxyhq/jackson:tagwillbereplaced
|
||||
imagePullPolicy: IfNotPresent
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
bases:
|
||||
- ../../base
|
||||
|
||||
resources:
|
||||
- ./secrets.yaml
|
||||
- ./mocksaml-secrets.yaml
|
||||
- ./mocksaml-deployment.yaml
|
||||
- ./migratepg-job.yaml
|
||||
|
||||
patches:
|
||||
- ./jackson-deployment.yaml
|
||||
|
||||
images:
|
||||
- name: boxyhq/jackson
|
||||
newTag: 1.14.0
|
||||
- name: boxyhq/mock-saml
|
||||
newTag: 1.1.7
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: jackson-migrate-pg
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: 'OnFailure'
|
||||
containers:
|
||||
- name: db
|
||||
image: boxyhq/jackson:tagwillbereplaced
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
- migrate.sh
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: jackson
|
|
@ -0,0 +1,44 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mocksaml
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: mocksaml
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mocksaml
|
||||
tier: mocksaml
|
||||
spec:
|
||||
containers:
|
||||
- name: mocksaml
|
||||
image: boxyhq/mock-saml:tagwillbereplaced
|
||||
imagePullPolicy: IfNotPresent
|
||||
startupProbe:
|
||||
httpGet:
|
||||
port: 4000
|
||||
path: /api/health
|
||||
periodSeconds: 10
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
port: 4000
|
||||
path: /api/health
|
||||
periodSeconds: 30
|
||||
failureThreshold: 5
|
||||
successThreshold: 2
|
||||
ports:
|
||||
- containerPort: 4000
|
||||
name: http
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: mocksaml
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
limits:
|
||||
cpu: 500m
|
|
@ -0,0 +1,24 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: jackson
|
||||
labels:
|
||||
app: jackson
|
||||
tier: jackson
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:eu-central-1:511214097407:certificate/3fc4272a-d97c-4bf0-8f8a-3425fda27e31'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 5225
|
||||
selector:
|
||||
app: jackson
|
||||
tier: jackson
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
bases:
|
||||
- ../../../base/services/internal
|
||||
- ./jackson-service.yaml
|
||||
- ./mocksaml-service.yaml
|
|
@ -0,0 +1,24 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mocksaml
|
||||
labels:
|
||||
app: mocksaml
|
||||
tier: mocksaml
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: nlb
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: 'arn:aws:acm:eu-central-1:511214097407:certificate/7eab394e-9a01-46c4-b941-82288a4479e9'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: '443'
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 4000
|
||||
selector:
|
||||
app: mocksaml
|
||||
tier: mocksaml
|
|
@ -43,6 +43,7 @@ const db: DatabaseOption = {
|
|||
readCapacityUnits: process.env.DB_DYNAMODB_RCUS ? Number(process.env.DB_DYNAMODB_RCUS) : undefined,
|
||||
writeCapacityUnits: process.env.DB_DYNAMODB_RCUS ? Number(process.env.DB_DYNAMODB_WCUS) : undefined,
|
||||
},
|
||||
manualMigration: process.env.DB_MANUAL_MIGRATION === 'true',
|
||||
};
|
||||
|
||||
const jacksonOptions: JacksonOption = {
|
||||
|
|
15
lib/utils.ts
15
lib/utils.ts
|
@ -61,14 +61,13 @@ export const strategyChecker = (req: NextApiRequest): { isSAML: boolean; isOIDC:
|
|||
|
||||
// The oidcMetadata JSON will be parsed here
|
||||
export const oidcMetadataParse = (
|
||||
body:
|
||||
| (
|
||||
| OIDCSSOConnectionWithDiscoveryUrl
|
||||
| (Omit<OIDCSSOConnectionWithMetadata, 'oidcMetadata'> & { oidcMetadata: string })
|
||||
) & {
|
||||
clientID: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
body: (
|
||||
| OIDCSSOConnectionWithDiscoveryUrl
|
||||
| (Omit<OIDCSSOConnectionWithMetadata, 'oidcMetadata'> & { oidcMetadata: string })
|
||||
) & {
|
||||
clientID: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
) => {
|
||||
if (!body.oidcDiscoveryUrl && typeof body.oidcMetadata === 'string') {
|
||||
try {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Initiating Migration..."
|
||||
export NODE_PATH=$(npm root -g)
|
||||
|
||||
cd ./npm
|
||||
if [ "$DB_ENGINE" = "mongo" ]
|
||||
then
|
||||
migrate-mongo up
|
||||
else
|
||||
ts-node --transpile-only --project tsconfig.json $NODE_PATH/typeorm/cli.js migration:run -d ./typeorm.ts
|
||||
fi
|
||||
if [ $? -eq 1 ]
|
||||
then
|
||||
echo "Migration Failed..."
|
||||
exit 1
|
||||
fi
|
||||
echo "Migration Finished..."
|
||||
|
||||
cd ..
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
dist
|
||||
npm/dist
|
|
@ -0,0 +1,22 @@
|
|||
const config = {
|
||||
mongodb: {
|
||||
url: process.env.DB_URL || 'mongodb://localhost:27017/jackson',
|
||||
options: {
|
||||
useNewUrlParser: true, // removes a deprecation warning when connecting
|
||||
useUnifiedTopology: true, // removes a deprecating warning when connecting
|
||||
// connectTimeoutMS: 3600000, // increase connection timeout to 1 hour
|
||||
// socketTimeoutMS: 3600000, // increase socket timeout to 1 hour
|
||||
},
|
||||
},
|
||||
|
||||
migrationsDir: 'migration/mongo',
|
||||
changelogCollectionName: 'changelog',
|
||||
migrationFileExtension: '.js',
|
||||
// Enable the algorithm to create a checksum of the file contents and use that in the comparison to determine
|
||||
// if the file should be run. Requires that scripts are coded to be run multiple times.
|
||||
useFileHash: false,
|
||||
// Don't change this, unless you know what you're doing
|
||||
moduleSystem: 'commonjs',
|
||||
};
|
||||
|
||||
module.exports = config;
|
|
@ -1,23 +0,0 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class Initial1640877418166 implements MigrationInterface {
|
||||
name = 'Initial1640877418166'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE \`jackson_store\` (\`key\` varchar(1500) NOT NULL, \`value\` text NOT NULL, \`iv\` varchar(64) NULL, \`tag\` varchar(64) NULL, PRIMARY KEY (\`key\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`CREATE TABLE \`jackson_index\` (\`id\` int NOT NULL AUTO_INCREMENT, \`key\` varchar(1500) NOT NULL, \`storeKey\` varchar(1500) NOT NULL, INDEX \`_jackson_index_key\` (\`key\`), INDEX \`_jackson_index_key_store\` (\`key\`, \`storeKey\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`CREATE TABLE \`jackson_ttl\` (\`key\` varchar(1500) NOT NULL, \`expiresAt\` bigint NOT NULL, INDEX \`_jackson_ttl_expires_at\` (\`expiresAt\`), PRIMARY KEY (\`key\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_index\` ADD CONSTRAINT \`FK_937b040fb2592b4671cbde09e83\` FOREIGN KEY (\`storeKey\`) REFERENCES \`jackson_store\`(\`key\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_index\` DROP FOREIGN KEY \`FK_937b040fb2592b4671cbde09e83\``);
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_ttl_expires_at\` ON \`jackson_ttl\``);
|
||||
await queryRunner.query(`DROP TABLE \`jackson_ttl\``);
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_index_key_store\` ON \`jackson_index\``);
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_index_key\` ON \`jackson_index\``);
|
||||
await queryRunner.query(`DROP TABLE \`jackson_index\``);
|
||||
await queryRunner.query(`DROP TABLE \`jackson_store\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class createdAt1644332636666 implements MigrationInterface {
|
||||
name = 'createdAt1644332636666'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`createdAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP()`);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`modifiedAt\` timestamp NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`modifiedAt\``);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`createdAt\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MdNamespace1692767993709 implements MigrationInterface {
|
||||
name = 'MdNamespace1692767993709'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`namespace\` varchar(64) NULL`);
|
||||
await queryRunner.query(`CREATE INDEX \`_jackson_store_namespace\` ON \`jackson_store\` (\`namespace\`)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_store_namespace\` ON \`jackson_store\``);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`namespace\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
module.exports = {
|
||||
async up(db, client) {
|
||||
const collection = db.collection('jacksonStore');
|
||||
const response = await collection.distinct('_id', {});
|
||||
const searchTerm = ':';
|
||||
for (const k in response) {
|
||||
const key = response[k].toString();
|
||||
const tokens2 = key.split(searchTerm).slice(0, 2);
|
||||
const value = tokens2.join(searchTerm);
|
||||
await db.collection('jacksonStore').updateOne({ _id: key }, {$set: { namespace: value }});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async down(db, client) {
|
||||
const collection = db.collection('jacksonStore');
|
||||
const response = await collection.distinct('_id', {});
|
||||
for (const k in response) {
|
||||
const key = response[k].toString();
|
||||
await db.collection('jacksonStore').updateOne({ _id: key }, {$set: { namespace: '' }});
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MssNamespace1692767993709 implements MigrationInterface {
|
||||
name = 'MssNamespace1692767993709'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "jackson_store" ADD "namespace" varchar(64)`);
|
||||
await queryRunner.query(`CREATE INDEX "_jackson_store_namespace" ON "jackson_store" ("namespace") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "_jackson_store_namespace" ON "jackson_store"`);
|
||||
await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "namespace"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class namespace1692817789888 implements MigrationInterface {
|
||||
name = 'namespace1692817789888'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const response = await queryRunner.query("select jackson.[key] from jackson_store jackson")
|
||||
const searchTerm = ':';
|
||||
for (const k in response) {
|
||||
const key = response[k].key;
|
||||
const tokens2 = key.split(searchTerm).slice(0, 2);
|
||||
const value = tokens2.join(searchTerm);
|
||||
queryRunner.query(`update jackson_store set namespace = '${value}' where jackson_store.[key] = '${key}'`)
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const response = await queryRunner.query("select jackson.[key] from jackson_store jackson")
|
||||
for (const k in response) {
|
||||
const key = response[k].key;
|
||||
queryRunner.query(`update jackson_store set namespace = NULL where jackson_store.[key] = '${key}'`)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class Initial1640877358925 implements MigrationInterface {
|
||||
name = 'Initial1640877358925'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE \`jackson_store\` (\`key\` varchar(1500) NOT NULL, \`value\` text NOT NULL, \`iv\` varchar(64) NULL, \`tag\` varchar(64) NULL, PRIMARY KEY (\`key\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`CREATE TABLE \`jackson_index\` (\`id\` int NOT NULL AUTO_INCREMENT, \`key\` varchar(1500) NOT NULL, \`storeKey\` varchar(1500) NOT NULL, INDEX \`_jackson_index_key\` (\`key\`), INDEX \`_jackson_index_key_store\` (\`key\`, \`storeKey\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`CREATE TABLE \`jackson_ttl\` (\`key\` varchar(1500) NOT NULL, \`expiresAt\` bigint NOT NULL, INDEX \`_jackson_ttl_expires_at\` (\`expiresAt\`), PRIMARY KEY (\`key\`)) ENGINE=InnoDB`);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_index\` ADD CONSTRAINT \`FK_937b040fb2592b4671cbde09e83\` FOREIGN KEY (\`storeKey\`) REFERENCES \`jackson_store\`(\`key\`) ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_index\` DROP FOREIGN KEY \`FK_937b040fb2592b4671cbde09e83\``);
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_ttl_expires_at\` ON \`jackson_ttl\``);
|
||||
await queryRunner.query(`DROP TABLE \`jackson_ttl\``);
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_index_key_store\` ON \`jackson_index\``);
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_index_key\` ON \`jackson_index\``);
|
||||
await queryRunner.query(`DROP TABLE \`jackson_index\``);
|
||||
await queryRunner.query(`DROP TABLE \`jackson_store\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import {MigrationInterface, QueryRunner} from "typeorm";
|
||||
|
||||
export class createdAt1644332641078 implements MigrationInterface {
|
||||
name = 'createdAt1644332641078'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`createdAt\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP`);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`modifiedAt\` timestamp NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`modifiedAt\``);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`createdAt\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class msNamespace1692767993709 implements MigrationInterface {
|
||||
name = 'msNamespace1692767993709'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`namespace\` varchar(64) NULL`);
|
||||
await queryRunner.query(`CREATE INDEX \`_jackson_store_namespace\` ON \`jackson_store\` (\`namespace\`)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_store_namespace\` ON \`jackson_store\``);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`namespace\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class MsNamespace1692767993709 implements MigrationInterface {
|
||||
name = 'MsNamespace1692767993709'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` ADD \`namespace\` varchar(64) NULL`);
|
||||
await queryRunner.query(`CREATE INDEX \`_jackson_store_namespace\` ON \`jackson_store\` (\`namespace\`)`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX \`_jackson_store_namespace\` ON \`jackson_store\``);
|
||||
await queryRunner.query(`ALTER TABLE \`jackson_store\` DROP COLUMN \`namespace\``);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class PgNamespace1692767993709 implements MigrationInterface {
|
||||
name = 'PgNamespace1692767993709'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "jackson_store" ADD "namespace" character varying(64)`);
|
||||
await queryRunner.query(`CREATE INDEX "_jackson_store_namespace" ON "jackson_store" ("namespace") `);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."_jackson_store_namespace"`);
|
||||
await queryRunner.query(`ALTER TABLE "jackson_store" DROP COLUMN "namespace"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||
|
||||
export class namespace1692817789888 implements MigrationInterface {
|
||||
name = 'namespace1692817789888'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const response = await queryRunner.query("select jackson.key from jackson_store jackson")
|
||||
const searchTerm = ':';
|
||||
for (const k in response) {
|
||||
const key = response[k].key;
|
||||
const tokens2 = key.split(searchTerm).slice(0, 2);
|
||||
const value = tokens2.join(searchTerm);
|
||||
queryRunner.query(`update jackson_store set namespace = '${value}' where jackson_store.key = '${key}'`)
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const response = await queryRunner.query("select jackson.key from jackson_store jackson")
|
||||
for (const k in response) {
|
||||
const key = response[k].key;
|
||||
queryRunner.query(`update jackson_store set namespace = NULL where jackson_store.key = '${key}'`)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -20,13 +20,16 @@
|
|||
"build": "tsc -p tsconfig.build.json",
|
||||
"db:migration:generate:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/postgres/pg_${MIGRATION_NAME}",
|
||||
"db:migration:generate:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mysql/ms_${MIGRATION_NAME}",
|
||||
"db:migration:generate:planetscale": "cross-env DB_ENGINE=planetscale DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mysql/ms_${MIGRATION_NAME}",
|
||||
"db:migration:generate:planetscale": "cross-env DB_ENGINE=planetscale DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/planetscale/ms_${MIGRATION_NAME}",
|
||||
"db:migration:generate:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mariadb/md_${MIGRATION_NAME}",
|
||||
"db:migration:generate:mongo": "migrate-mongo create ${MIGRATION_NAME}",
|
||||
"db:migration:generate:mssql": "cross-env DB_TYPE=mssql DB_URL='sqlserver://localhost:1433;database=master;username=sa;password=123ABabc!' ts-node --transpile-only ./node_modules/typeorm/cli.js migration:generate -d typeorm.ts migration/mssql/mss_${MIGRATION_NAME}",
|
||||
"db:migration:run:postgres": "ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
|
||||
"db:migration:run:mysql": "cross-env DB_TYPE=mysql DB_URL=mysql://root:mysql@localhost:3307/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
|
||||
"db:migration:run:planetscale": "cross-env DB_SSL=true DB_ENGINE=planetscale DB_URL=${PLANETSCALE_URL} ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
|
||||
"db:migration:run:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
|
||||
"db:migration:run:mongo": "cross-env DB_URL='mongodb://localhost:27017/jackson' migrate-mongo up",
|
||||
"db:migration:revert:mongo": "cross-env DB_URL='mongodb://localhost:27017/jackson migrate-mongo down",
|
||||
"db:migration:run:mssql": "cross-env DB_TYPE=mssql DB_URL='sqlserver://localhost:1433;database=master;username=sa;password=123ABabc!' ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "cross-env BOXYHQ_NO_ANALYTICS=1 tap --timeout=0 --allow-incomplete-coverage --allow-empty-coverage test/**/*.test.ts",
|
||||
|
@ -36,23 +39,23 @@
|
|||
"coverage-map": "map.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-dynamodb": "3.425.0",
|
||||
"@aws-sdk/credential-providers": "3.425.0",
|
||||
"@aws-sdk/util-dynamodb": "3.425.0",
|
||||
"@aws-sdk/client-dynamodb": "3.429.0",
|
||||
"@aws-sdk/credential-providers": "3.429.0",
|
||||
"@aws-sdk/util-dynamodb": "3.429.0",
|
||||
"@boxyhq/error-code-mnemonic": "0.1.1",
|
||||
"@boxyhq/metrics": "0.2.5",
|
||||
"@boxyhq/saml20": "1.2.4",
|
||||
"@googleapis/admin": "12.4.0",
|
||||
"@googleapis/admin": "13.0.0",
|
||||
"axios": "1.5.1",
|
||||
"encoding": "0.1.13",
|
||||
"jose": "4.15.2",
|
||||
"jose": "4.15.4",
|
||||
"lodash": "4.17.21",
|
||||
"mixpanel": "0.18.0",
|
||||
"mongodb": "6.1.0",
|
||||
"mssql": "10.0.1",
|
||||
"mysql2": "3.6.1",
|
||||
"mysql2": "3.6.2",
|
||||
"node-forge": "1.3.1",
|
||||
"openid-client": "5.6.0",
|
||||
"openid-client": "5.6.1",
|
||||
"pg": "8.11.3",
|
||||
"redis": "4.6.10",
|
||||
"reflect-metadata": "0.1.13",
|
||||
|
@ -62,15 +65,16 @@
|
|||
"xmlbuilder": "15.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "8.1.0",
|
||||
"@faker-js/faker": "8.2.0",
|
||||
"@types/lodash": "4.14.199",
|
||||
"@types/node": "20.8.2",
|
||||
"@types/sinon": "10.0.18",
|
||||
"@types/node": "20.8.6",
|
||||
"@types/sinon": "10.0.19",
|
||||
"@types/tap": "15.0.9",
|
||||
"cross-env": "7.0.3",
|
||||
"nock": "13.3.3",
|
||||
"migrate-mongo": "11.0.0",
|
||||
"nock": "13.3.4",
|
||||
"sinon": "16.1.0",
|
||||
"tap": "18.4.3",
|
||||
"tap": "18.5.2",
|
||||
"ts-node": "10.9.1",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.2.2"
|
||||
|
|
|
@ -31,9 +31,12 @@ export class AnalyticsController {
|
|||
await this.send();
|
||||
}
|
||||
|
||||
setInterval(async () => {
|
||||
await this.send();
|
||||
}, 60 * 60 * 24 * 1000);
|
||||
setInterval(
|
||||
async () => {
|
||||
await this.send();
|
||||
},
|
||||
60 * 60 * 24 * 1000
|
||||
);
|
||||
}
|
||||
|
||||
async send() {
|
||||
|
@ -45,9 +48,12 @@ export class AnalyticsController {
|
|||
},
|
||||
(err: Error | undefined) => {
|
||||
if (err) {
|
||||
setTimeout(() => {
|
||||
this.send();
|
||||
}, 1000 * 60 * 60);
|
||||
setTimeout(
|
||||
() => {
|
||||
this.send();
|
||||
},
|
||||
1000 * 60 * 60
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -486,7 +486,7 @@ export class OAuthController implements IOAuthController {
|
|||
|
||||
public async samlResponse(
|
||||
body: SAMLResponsePayload
|
||||
): Promise<{ redirect_url?: string; app_select_form?: string; responseForm?: string }> {
|
||||
): Promise<{ redirect_url?: string; app_select_form?: string; response_form?: string }> {
|
||||
let connection: SAMLSSORecord | undefined;
|
||||
let rawResponse: string | undefined;
|
||||
let sessionId: string | undefined;
|
||||
|
@ -535,7 +535,7 @@ export class OAuthController implements IOAuthController {
|
|||
}
|
||||
|
||||
isSAMLFederated = session && 'samlFederated' in session;
|
||||
const isSPFflow = !isIdPFlow && !isSAMLFederated;
|
||||
const isSPFlow = !isIdPFlow && !isSAMLFederated;
|
||||
|
||||
// IdP initiated SSO flow
|
||||
if (isIdPFlow) {
|
||||
|
@ -563,7 +563,7 @@ export class OAuthController implements IOAuthController {
|
|||
|
||||
// SP initiated SSO flow
|
||||
// Resolve if there are multiple matches for SP login
|
||||
if (isSPFflow) {
|
||||
if (isSPFlow || isSAMLFederated) {
|
||||
connection = connections.filter((c) => {
|
||||
return (
|
||||
c.clientID === session.requested.client_id ||
|
||||
|
@ -572,10 +572,6 @@ export class OAuthController implements IOAuthController {
|
|||
})[0];
|
||||
}
|
||||
|
||||
if (!connection) {
|
||||
connection = connections[0];
|
||||
}
|
||||
|
||||
if (!connection) {
|
||||
throw new JacksonError('SAML connection not found.', 403);
|
||||
}
|
||||
|
@ -630,7 +626,7 @@ export class OAuthController implements IOAuthController {
|
|||
|
||||
await this.sessionStore.delete(sessionId);
|
||||
|
||||
return { responseForm };
|
||||
return { response_form: responseForm };
|
||||
}
|
||||
|
||||
const code = await this._buildAuthorizationCode(connection, profile, session, isIdPFlow);
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
export const redirect = (
|
||||
redirectUrl: string,
|
||||
redirectUrls: string[]
|
||||
): boolean => {
|
||||
export const redirect = (redirectUrl: string, redirectUrls: string[]): boolean => {
|
||||
const url: URL = new URL(redirectUrl);
|
||||
|
||||
for (const idx in redirectUrls) {
|
||||
|
@ -9,11 +6,7 @@ export const redirect = (
|
|||
|
||||
// TODO: Check pathname, for now pathname is ignored
|
||||
|
||||
if (
|
||||
rUrl.protocol === url.protocol &&
|
||||
rUrl.hostname === url.hostname &&
|
||||
rUrl.port === url.port
|
||||
) {
|
||||
if (rUrl.protocol === url.protocol && rUrl.hostname === url.hostname && rUrl.port === url.port) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,5 @@ export const transformBase64 = (input: string): string => {
|
|||
};
|
||||
|
||||
export const encode = (code_challenge: string): string => {
|
||||
return transformBase64(
|
||||
crypto.createHash('sha256').update(code_challenge).digest('base64')
|
||||
);
|
||||
return transformBase64(crypto.createHash('sha256').update(code_challenge).digest('base64'));
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import { JacksonError } from './error';
|
|||
import { IndexNames } from './utils';
|
||||
import { relayStatePrefix } from './utils';
|
||||
import { createSAMLResponse } from '../saml/lib';
|
||||
import * as redirect from './oauth/redirect';
|
||||
|
||||
const deflateRawAsync = promisify(deflateRaw);
|
||||
|
||||
|
@ -134,21 +135,34 @@ export class SAMLHandler {
|
|||
return { connection: connections[0] };
|
||||
}
|
||||
|
||||
async createSAMLRequest(params: {
|
||||
connection: SAMLSSORecord;
|
||||
requestParams: Record<string, any>;
|
||||
}): Promise<{ redirectUrl: string }> {
|
||||
async createSAMLRequest(params: { connection: SAMLSSORecord; requestParams: Record<string, any> }) {
|
||||
const { connection, requestParams } = params;
|
||||
|
||||
// We have a connection now, so we can create the SAML request
|
||||
const certificate = await getDefaultCertificate();
|
||||
|
||||
const { sso } = connection.idpMetadata;
|
||||
|
||||
let ssoUrl;
|
||||
let post = false;
|
||||
|
||||
if ('redirectUrl' in sso) {
|
||||
ssoUrl = sso.redirectUrl;
|
||||
} else if ('postUrl' in sso) {
|
||||
ssoUrl = sso.postUrl;
|
||||
post = true;
|
||||
}
|
||||
|
||||
const samlRequest = saml.request({
|
||||
ssoUrl: connection.idpMetadata.sso.redirectUrl,
|
||||
ssoUrl,
|
||||
entityID: `${this.opts.samlAudience}`,
|
||||
callbackUrl: `${this.opts.externalUrl}/api/oauth/saml`,
|
||||
callbackUrl: this.opts.externalUrl + this.opts.samlPath,
|
||||
signingKey: certificate.privateKey,
|
||||
publicKey: certificate.publicKey,
|
||||
forceAuthn: !!connection.forceAuthn,
|
||||
identifierFormat: connection.identifierFormat
|
||||
? connection.identifierFormat
|
||||
: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
|
||||
});
|
||||
|
||||
// Create a new session to store SP request information
|
||||
|
@ -156,23 +170,40 @@ export class SAMLHandler {
|
|||
|
||||
await this.session.put(sessionId, {
|
||||
id: samlRequest.id,
|
||||
request: {
|
||||
requested: {
|
||||
...requestParams,
|
||||
client_id: connection.clientID,
|
||||
},
|
||||
samlFederated: true,
|
||||
});
|
||||
|
||||
// Create URL to redirect to the Identity Provider
|
||||
const url = new URL(`${connection.idpMetadata.sso.redirectUrl}`);
|
||||
const relayState = `${relayStatePrefix}${sessionId}`;
|
||||
|
||||
url.searchParams.set('RelayState', `${relayStatePrefix}${sessionId}`);
|
||||
url.searchParams.set(
|
||||
'SAMLRequest',
|
||||
Buffer.from(await deflateRawAsync(samlRequest.request)).toString('base64')
|
||||
);
|
||||
let redirectUrl;
|
||||
let authorizeForm;
|
||||
|
||||
// Decide whether to use HTTP Redirect or HTTP POST binding
|
||||
if (!post) {
|
||||
redirectUrl = redirect.success(ssoUrl, {
|
||||
RelayState: relayState,
|
||||
SAMLRequest: Buffer.from(await deflateRawAsync(samlRequest.request)).toString('base64'),
|
||||
});
|
||||
} else {
|
||||
authorizeForm = saml.createPostForm(ssoUrl, [
|
||||
{
|
||||
name: 'RelayState',
|
||||
value: relayState,
|
||||
},
|
||||
{
|
||||
name: 'SAMLRequest',
|
||||
value: Buffer.from(samlRequest.request).toString('base64'),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
return {
|
||||
redirectUrl: url.toString(),
|
||||
redirect_url: redirectUrl,
|
||||
authorize_form: authorizeForm,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -183,18 +214,18 @@ export class SAMLHandler {
|
|||
|
||||
try {
|
||||
const responseSigned = await createSAMLResponse({
|
||||
audience: session.request.entityId,
|
||||
acsUrl: session.request.acsUrl,
|
||||
requestId: session.request.id,
|
||||
audience: session.requested.entityId,
|
||||
acsUrl: session.requested.acsUrl,
|
||||
requestId: session.requested.id,
|
||||
issuer: `${this.opts.samlAudience}`,
|
||||
profile,
|
||||
...certificate,
|
||||
});
|
||||
|
||||
const responseForm = saml.createPostForm(session.request.acsUrl, [
|
||||
const responseForm = saml.createPostForm(session.requested.acsUrl, [
|
||||
{
|
||||
name: 'RelayState',
|
||||
value: session.request.relayState,
|
||||
value: session.requested.relayState,
|
||||
},
|
||||
{
|
||||
name: 'SAMLResponse',
|
||||
|
|
|
@ -11,6 +11,7 @@ export default function defaultDb(opts: JacksonOption) {
|
|||
opts.db.dynamodb.region = opts.db.dynamodb.region || 'us-east-1';
|
||||
opts.db.dynamodb.readCapacityUnits = opts.db.dynamodb.readCapacityUnits || 5;
|
||||
opts.db.dynamodb.writeCapacityUnits = opts.db.dynamodb.writeCapacityUnits || 5;
|
||||
opts.db.manualMigration = opts.db.manualMigration || false;
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ type _Document = {
|
|||
value: Encrypted;
|
||||
expiresAt?: Date;
|
||||
modifiedAt: string;
|
||||
namespace: string;
|
||||
indexes: string[];
|
||||
};
|
||||
|
||||
|
@ -34,10 +35,38 @@ class Mongo implements DatabaseDriver {
|
|||
|
||||
await this.collection.createIndex({ indexes: 1 });
|
||||
await this.collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 1 });
|
||||
await this.collection.createIndex({ namespace: 1 });
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
if (!this.options.manualMigration) {
|
||||
await this.indexNamespace();
|
||||
}
|
||||
break;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`error in index namespace execution for db engine: ${this.options.engine}, err: ${err}`
|
||||
);
|
||||
await dbutils.sleep(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async indexNamespace() {
|
||||
const docs = await this.collection.find({ namespace: { $exists: false } }).toArray();
|
||||
const searchTerm = ':';
|
||||
|
||||
for (const doc of docs || []) {
|
||||
const tokens2 = doc._id.toString().split(searchTerm).slice(0, 2);
|
||||
const namespace = tokens2.join(searchTerm);
|
||||
await this.collection.updateOne({ _id: doc._id }, { $set: { namespace } });
|
||||
}
|
||||
}
|
||||
|
||||
async get(namespace: string, key: string): Promise<any> {
|
||||
const res = await this.collection.findOne({
|
||||
_id: dbutils.key(namespace, key) as any,
|
||||
|
@ -51,9 +80,8 @@ class Mongo implements DatabaseDriver {
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async getAll(namespace: string, pageOffset?: number, pageLimit?: number, _?: string): Promise<Records> {
|
||||
const _namespaceMatch = new RegExp(`^${namespace}:.*`);
|
||||
const docs = await this.collection
|
||||
.find({ _id: _namespaceMatch }, { sort: { createdAt: -1 }, skip: pageOffset, limit: pageLimit })
|
||||
.find({ namespace: namespace }, { sort: { createdAt: -1 }, skip: pageOffset, limit: pageLimit })
|
||||
.toArray();
|
||||
|
||||
if (docs) {
|
||||
|
@ -102,7 +130,7 @@ class Mongo implements DatabaseDriver {
|
|||
if (ttl) {
|
||||
doc.expiresAt = new Date(Date.now() + ttl * 1000);
|
||||
}
|
||||
|
||||
doc.namespace = namespace;
|
||||
// no ttl support for secondary indexes
|
||||
for (const idx of indexes || []) {
|
||||
const idxKey = dbutils.keyForIndex(namespace, idx);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
|
||||
|
||||
@Index('_jackson_index_key_store', ['key', 'storeKey'])
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_index' })
|
||||
export class JacksonIndex {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column } from 'typeorm';
|
||||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_store' })
|
||||
export class JacksonStore {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
@ -40,4 +40,12 @@ export class JacksonStore {
|
|||
nullable: true,
|
||||
})
|
||||
modifiedAt?: string;
|
||||
|
||||
@Index('_jackson_store_namespace')
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 64,
|
||||
nullable: true,
|
||||
})
|
||||
namespace?: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_ttl' })
|
||||
export class JacksonTTL {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { JacksonStore } from './JacksonStore';
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
|
||||
|
||||
@Index('_jackson_index_key_store', ['key', 'storeKey'])
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_index' })
|
||||
export class JacksonIndex {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column } from 'typeorm';
|
||||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_store' })
|
||||
export class JacksonStore {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
@ -40,4 +40,12 @@ export class JacksonStore {
|
|||
nullable: true,
|
||||
})
|
||||
modifiedAt?: string;
|
||||
|
||||
@Index('_jackson_store_namespace')
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 64,
|
||||
nullable: true,
|
||||
})
|
||||
namespace?: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_ttl' })
|
||||
export class JacksonTTL {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { JacksonStore } from './JacksonStore';
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
|
||||
|
||||
@Index('_jackson_index_key_store', ['key', 'storeKey'])
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_index' })
|
||||
export class JacksonIndex {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column } from 'typeorm';
|
||||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_store' })
|
||||
export class JacksonStore {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
@ -40,4 +40,12 @@ export class JacksonStore {
|
|||
nullable: true,
|
||||
})
|
||||
modifiedAt?: string;
|
||||
|
||||
@Index('_jackson_store_namespace')
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 64,
|
||||
nullable: true,
|
||||
})
|
||||
namespace?: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_ttl' })
|
||||
export class JacksonTTL {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { JacksonStore } from './JacksonStore';
|
|||
import { Entity, PrimaryGeneratedColumn, Column, Index, ManyToOne } from 'typeorm';
|
||||
|
||||
@Index('_jackson_index_key_store', ['key', 'storeKey'])
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_index' })
|
||||
export class JacksonIndex {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: number;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column } from 'typeorm';
|
||||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_store' })
|
||||
export class JacksonStore {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
@ -40,4 +40,12 @@ export class JacksonStore {
|
|||
nullable: true,
|
||||
})
|
||||
modifiedAt?: string;
|
||||
|
||||
@Index('_jackson_store_namespace')
|
||||
@Column({
|
||||
type: 'varchar',
|
||||
length: 64,
|
||||
nullable: true,
|
||||
})
|
||||
namespace?: string;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column, Index } from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
@Entity({ name: 'jackson_ttl' })
|
||||
export class JacksonTTL {
|
||||
@Column({
|
||||
primary: true,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require('reflect-metadata');
|
||||
|
||||
import { DatabaseDriver, DatabaseOption, Index, Encrypted, Records } from '../../typings';
|
||||
import { DataSource, DataSourceOptions, Like, In } from 'typeorm';
|
||||
import { DataSource, DataSourceOptions, In, IsNull } from 'typeorm';
|
||||
import * as dbutils from '../utils';
|
||||
import * as mssql from './mssql';
|
||||
|
||||
|
@ -26,13 +26,17 @@ class Sql implements DatabaseDriver {
|
|||
|
||||
async init({ JacksonStore, JacksonIndex, JacksonTTL }): Promise<Sql> {
|
||||
const sqlType = this.options.engine === 'planetscale' ? 'mysql' : this.options.type!;
|
||||
// Synchronize by default for non-planetscale engines only if migrations are not set to run
|
||||
let synchronize = !this.options.manualMigration;
|
||||
if (this.options.engine === 'planetscale') {
|
||||
synchronize = false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const baseOpts = {
|
||||
type: sqlType,
|
||||
synchronize: this.options.engine !== 'planetscale',
|
||||
migrationsTableName: '_jackson_migrations',
|
||||
synchronize,
|
||||
logging: ['error'],
|
||||
entities: [JacksonStore, JacksonIndex, JacksonTTL],
|
||||
};
|
||||
|
@ -73,6 +77,21 @@ class Sql implements DatabaseDriver {
|
|||
this.indexRepository = this.dataSource.getRepository(JacksonIndex);
|
||||
this.ttlRepository = this.dataSource.getRepository(JacksonTTL);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
if (synchronize) {
|
||||
await this.indexNamespace();
|
||||
}
|
||||
break;
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`error in index namespace execution for engine: ${this.options.engine}, type: ${sqlType} err: ${err}`
|
||||
);
|
||||
await dbutils.sleep(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.ttl && this.options.cleanupLimit) {
|
||||
this.ttlCleanup = async () => {
|
||||
const now = Date.now();
|
||||
|
@ -111,6 +130,23 @@ class Sql implements DatabaseDriver {
|
|||
return this;
|
||||
}
|
||||
|
||||
async indexNamespace() {
|
||||
const res = await this.storeRepository.find({
|
||||
where: {
|
||||
namespace: IsNull(),
|
||||
},
|
||||
select: ['key'],
|
||||
});
|
||||
const searchTerm = ':';
|
||||
|
||||
for (const r of res) {
|
||||
const key = r.key;
|
||||
const tokens2 = key.split(searchTerm).slice(0, 2);
|
||||
const value = tokens2.join(searchTerm);
|
||||
await this.storeRepository.update({ key }, { namespace: value });
|
||||
}
|
||||
}
|
||||
|
||||
async get(namespace: string, key: string): Promise<any> {
|
||||
const res = await this.storeRepository.findOneBy({
|
||||
key: dbutils.key(namespace, key),
|
||||
|
@ -131,7 +167,7 @@ class Sql implements DatabaseDriver {
|
|||
async getAll(namespace: string, pageOffset?: number, pageLimit?: number, _?: string): Promise<Records> {
|
||||
const skipOffsetAndLimitValue = !dbutils.isNumeric(pageOffset) && !dbutils.isNumeric(pageLimit);
|
||||
const res = await this.storeRepository.find({
|
||||
where: { key: Like(`%${namespace}%`) },
|
||||
where: { namespace: namespace },
|
||||
select: ['value', 'iv', 'tag'],
|
||||
order: {
|
||||
['createdAt']: 'DESC',
|
||||
|
@ -195,6 +231,7 @@ class Sql implements DatabaseDriver {
|
|||
store.iv = val.iv;
|
||||
store.tag = val.tag;
|
||||
store.modifiedAt = new Date().toISOString();
|
||||
store.namespace = namespace;
|
||||
await transactionalEntityManager.save(store);
|
||||
|
||||
if (ttl) {
|
||||
|
|
|
@ -7,7 +7,10 @@ import type {
|
|||
} from '../typings';
|
||||
|
||||
export class RequestHandler {
|
||||
constructor(private directoryUsers: IDirectoryUsers, private directoryGroups: IDirectoryGroups) {}
|
||||
constructor(
|
||||
private directoryUsers: IDirectoryUsers,
|
||||
private directoryGroups: IDirectoryGroups
|
||||
) {}
|
||||
|
||||
async handle(request: DirectorySyncRequest, callback?: EventCallback): Promise<DirectorySyncResponse> {
|
||||
const resourceType = request.resourceType.toLowerCase();
|
||||
|
|
|
@ -157,11 +157,10 @@ export type UserPatchOperation = {
|
|||
export type GroupPatchOperation = {
|
||||
op: 'add' | 'remove' | 'replace';
|
||||
path?: 'members' | 'displayName';
|
||||
value:
|
||||
| {
|
||||
value: string;
|
||||
display?: string;
|
||||
}[];
|
||||
value: {
|
||||
value: string;
|
||||
display?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type GroupMembership = {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { JacksonError } from '../../controller/error';
|
|||
import { SAMLHandler } from '../../controller/saml-handler';
|
||||
import type { JacksonOption, SAMLSSORecord, SAMLTracerInstance } from '../../typings';
|
||||
import { extractSAMLRequestAttributes } from '../../saml/lib';
|
||||
import { getErrorMessage } from '../../controller/utils';
|
||||
import { getErrorMessage, isConnectionActive } from '../../controller/utils';
|
||||
import { throwIfInvalidLicense } from '../common/checkLicense';
|
||||
|
||||
export class SSO {
|
||||
|
@ -45,6 +45,7 @@ export class SSO {
|
|||
|
||||
let connection: SAMLSSORecord | undefined;
|
||||
let id, acsUrl, entityId, publicKey, providerName, decodedRequest, app;
|
||||
|
||||
try {
|
||||
const parsedSAMLRequest = await extractSAMLRequestAttributes(request);
|
||||
|
||||
|
@ -81,7 +82,8 @@ export class SSO {
|
|||
// If there is a redirect URL, then we need to redirect to that URL
|
||||
if ('redirectUrl' in response) {
|
||||
return {
|
||||
redirectUrl: response.redirectUrl,
|
||||
redirect_url: response.redirectUrl,
|
||||
authorize_form: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -94,6 +96,10 @@ export class SSO {
|
|||
throw new JacksonError('No SAML connection found.', 404);
|
||||
}
|
||||
|
||||
if (!isConnectionActive(connection)) {
|
||||
throw new JacksonError('SSO connection is deactivated. Please contact your administrator.', 403);
|
||||
}
|
||||
|
||||
return await this.samlHandler.createSAMLRequest({
|
||||
connection,
|
||||
requestParams: {
|
||||
|
@ -103,6 +109,8 @@ export class SSO {
|
|||
publicKey,
|
||||
providerName,
|
||||
relayState,
|
||||
tenant: app.tenant,
|
||||
product: app.product,
|
||||
},
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
|
|
|
@ -35,8 +35,8 @@ const mapping = [
|
|||
...arrayMapping,
|
||||
];
|
||||
|
||||
type attributes = typeof mapping[number]['attribute'];
|
||||
type schemas = typeof mapping[number]['schema'];
|
||||
type attributes = (typeof mapping)[number]['attribute'];
|
||||
type schemas = (typeof mapping)[number]['schema'];
|
||||
|
||||
const map = (claims: Record<attributes | schemas, unknown>) => {
|
||||
arrayMapping.forEach((m) => {
|
||||
|
|
|
@ -250,7 +250,7 @@ export const createSAMLResponse = async ({
|
|||
|
||||
const xml = xmlbuilder.create(nodes, { encoding: 'UTF-8' }).end();
|
||||
|
||||
return await saml.sign(
|
||||
return saml.sign(
|
||||
xml,
|
||||
privateKey,
|
||||
publicKey,
|
||||
|
|
|
@ -169,7 +169,7 @@ export interface IOAuthController {
|
|||
authorize(body: OAuthReq): Promise<{ redirect_url?: string; authorize_form?: string }>;
|
||||
samlResponse(
|
||||
body: SAMLResponsePayload
|
||||
): Promise<{ redirect_url?: string; app_select_form?: string; responseForm?: string }>;
|
||||
): Promise<{ redirect_url?: string; app_select_form?: string; response_form?: string }>;
|
||||
oidcAuthzResponse(body: OIDCAuthzResponsePayload): Promise<{ redirect_url?: string }>;
|
||||
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
||||
userInfo(token: string): Promise<Profile>;
|
||||
|
@ -378,6 +378,7 @@ export interface DatabaseOption {
|
|||
readCapacityUnits?: number;
|
||||
writeCapacityUnits?: number;
|
||||
};
|
||||
manualMigration?: boolean;
|
||||
}
|
||||
|
||||
export interface JacksonOption {
|
||||
|
|
|
@ -79,15 +79,15 @@ tap.test('Federated SAML flow', async (t) => {
|
|||
});
|
||||
|
||||
// Extract relay state created by Jackson
|
||||
jacksonRelayState = new URL(response.redirectUrl).searchParams.get('RelayState');
|
||||
jacksonRelayState = new URL(response.redirect_url).searchParams.get('RelayState');
|
||||
|
||||
t.ok(
|
||||
response.redirectUrl?.startsWith(`${connection.idpMetadata.sso.redirectUrl}`),
|
||||
response.redirect_url?.startsWith(`${connection.idpMetadata.sso.redirectUrl}`),
|
||||
'Should have a SSO URL that starts with IdP SSO URL'
|
||||
);
|
||||
t.ok(response.redirectUrl, 'Should have a redirect URL');
|
||||
t.ok(response.redirectUrl?.includes('SAMLRequest'), 'Should have a SAMLRequest in the redirect URL');
|
||||
t.ok(response.redirectUrl?.includes('RelayState'), 'Should have a RelayState in the redirect URL');
|
||||
t.ok(response.redirect_url, 'Should have a redirect URL');
|
||||
t.ok(response.redirect_url?.includes('SAMLRequest'), 'Should have a SAMLRequest in the redirect URL');
|
||||
t.ok(response.redirect_url?.includes('RelayState'), 'Should have a RelayState in the redirect URL');
|
||||
});
|
||||
|
||||
t.test('Should be able to accept SAML Response from IdP and generate SAML Response for SP', async (t) => {
|
||||
|
@ -110,15 +110,15 @@ tap.test('Federated SAML flow', async (t) => {
|
|||
});
|
||||
|
||||
t.ok(response);
|
||||
t.ok('responseForm' in response);
|
||||
t.ok('response_form' in response);
|
||||
t.ok(
|
||||
response.responseForm?.includes('SAMLResponse'),
|
||||
response.response_form?.includes('SAMLResponse'),
|
||||
'Should have a SAMLResponse in the response form'
|
||||
);
|
||||
t.ok(response.responseForm?.includes('RelayState'), 'Should have a RelayState in the response form');
|
||||
t.ok(response.response_form?.includes('RelayState'), 'Should have a RelayState in the response form');
|
||||
|
||||
const relayState = response.responseForm
|
||||
? response.responseForm.match(/<input type="hidden" name="RelayState" value="(.*)"\/>/)?.[1]
|
||||
const relayState = response.response_form
|
||||
? response.response_form.match(/<input type="hidden" name="RelayState" value="(.*)"\/>/)?.[1]
|
||||
: null;
|
||||
|
||||
t.match(relayState, relayStateFromSP, 'Should have the same relay state as the one sent by SP');
|
||||
|
|
|
@ -33,7 +33,8 @@ if (process.env.DB_SSL === 'true') {
|
|||
};
|
||||
}
|
||||
|
||||
const url = process.env.DB_URL || 'postgresql://postgres:postgres@localhost:5432/postgres';
|
||||
const url =
|
||||
process.env.DB_URL || process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/postgres';
|
||||
|
||||
let AppDataSource: DataSource;
|
||||
|
||||
|
@ -43,7 +44,10 @@ const baseOpts = {
|
|||
migrationsTableName: '_jackson_migrations',
|
||||
logging: 'all',
|
||||
entities: [`src/db/${entitiesDir}/entity/**/*.ts`],
|
||||
migrations: [`migration/${migrationsDir}/**/*.ts`],
|
||||
migrations:
|
||||
type === 'mssql'
|
||||
? [`migration/${migrationsDir}/**/*.ts`]
|
||||
: [`migration/${migrationsDir}/**/*.ts`, `migration/sql/**/*.ts`],
|
||||
};
|
||||
|
||||
if (type === 'mssql') {
|
||||
|
@ -59,7 +63,10 @@ if (type === 'mssql') {
|
|||
});
|
||||
} else {
|
||||
AppDataSource = new DataSource(<DataSourceOptions>{
|
||||
url: process.env.DB_URL || 'postgresql://postgres:postgres@localhost:5432/postgres',
|
||||
url:
|
||||
process.env.DB_URL ||
|
||||
process.env.DATABASE_URL ||
|
||||
'postgresql://postgres:postgres@localhost:5432/postgres',
|
||||
ssl,
|
||||
...baseOpts,
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jackson",
|
||||
"version": "1.13.0",
|
||||
"version": "1.14.0",
|
||||
"private": true,
|
||||
"description": "SAML 2.0 service",
|
||||
"keywords": [
|
||||
|
@ -15,7 +15,6 @@
|
|||
"dev": "cross-env JACKSON_API_KEYS=secret IDP_ENABLED=true next dev -p 5225",
|
||||
"dev-dbs": "docker-compose -f ./_dev/docker-compose.yml up -d",
|
||||
"dev-dbs-destroy": "docker-compose -f ./_dev/docker-compose.yml down --volumes --remove-orphans",
|
||||
"lint": "next lint && eslint -c .eslintrc.js --ext .ts ./",
|
||||
"mongo": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mongo DB_URL=mongodb://localhost:27017/jackson npm run dev",
|
||||
"pre-loaded": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=mem PRE_LOADED_CONNECTION='./_dev/saml_config' npm run dev",
|
||||
"pre-loaded-db": "cross-env JACKSON_API_KEYS=secret PRE_LOADED_CONNECTION='./_dev/saml_config' npm run dev",
|
||||
|
@ -32,8 +31,10 @@
|
|||
"mariadb:skaffold": "skaffold dev -f skaffold-mariadb.yaml --status-check=false --force=true",
|
||||
"mssql:skaffold": "skaffold dev -f skaffold-mssql.yaml --status-check=false --force=true",
|
||||
"dynamodb:skaffold": "skaffold dev -f skaffold-dynamodb.yaml --status-check=false --force=true",
|
||||
"demo:skaffold": "echo 'This is only meant for BoxyHQ internal use. Please use {dbname}:skaffold instead' && skaffold run -f skaffold-demo.yaml --status-check=false --force=true",
|
||||
"demo-services:skaffold": "echo 'This is only meant for BoxyHQ internal use. Please use {dbname}:skaffold instead' && skaffold run -f skaffold-demo-services.yaml --status-check=false --force=true",
|
||||
"demo:skaffold": "echo 'This is only meant for BoxyHQ internal use. Please use {dbname}:skaffold instead' && skaffold run -f skaffold-demo.yaml --status-check=false",
|
||||
"demo-services:skaffold": "echo 'This is only meant for BoxyHQ internal use. Please use {dbname}:skaffold instead' && skaffold run -f skaffold-demo-services.yaml --status-check=false",
|
||||
"prod-eu:skaffold": "echo 'This is only meant for BoxyHQ internal use. Please use {dbname}:skaffold instead' && skaffold run -f skaffold-prod-eu.yaml --status-check=false",
|
||||
"prod-eu-services:skaffold": "echo 'This is only meant for BoxyHQ internal use. Please use {dbname}:skaffold instead' && skaffold run -f skaffold-prod-eu-services.yaml --status-check=false",
|
||||
"start": "cross-env PORT=5225 NODE_OPTIONS=--dns-result-order=ipv4first node .next/standalone/server.js",
|
||||
"swagger-jsdoc": "swagger-jsdoc -d swagger/swaggerDefinition.js npm/src/**/*.ts npm/src/**/**/*.ts -o swagger/swagger.json",
|
||||
"redis": "cross-env JACKSON_API_KEYS=secret DB_ENGINE=redis DB_TYPE=redis DB_URL=redis://localhost:6379/redis npm run dev",
|
||||
|
@ -50,57 +51,61 @@
|
|||
"prebuild": "ts-node --logError prebuild.ts",
|
||||
"build": "next build",
|
||||
"postbuild": "ts-node --logError postbuild.ts",
|
||||
"release": "git checkout release && git merge origin/main && release-it"
|
||||
"release": "git checkout release && git merge origin/main && release-it",
|
||||
"check-types": "tsc --pretty --noEmit",
|
||||
"check-format": "prettier --check .",
|
||||
"check-lint": "next lint && eslint -c .eslintrc.js --ext ts --ext tsx --ext js ./",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@boxyhq/metrics": "0.2.5",
|
||||
"@boxyhq/react-ui": "3.3.12",
|
||||
"@boxyhq/react-ui": "3.3.14",
|
||||
"@boxyhq/saml-jackson": "file:npm",
|
||||
"@heroicons/react": "2.0.18",
|
||||
"@retracedhq/logs-viewer": "2.5.1",
|
||||
"@retracedhq/retraced": "0.7.0",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"axios": "1.5.1",
|
||||
"blockly": "10.2.1",
|
||||
"blockly": "10.2.2",
|
||||
"classnames": "2.3.2",
|
||||
"cors": "2.8.5",
|
||||
"daisyui": "3.9.2",
|
||||
"daisyui": "3.9.3",
|
||||
"i18next": "22.5.1",
|
||||
"medium-zoom": "1.0.8",
|
||||
"micromatch": "4.0.5",
|
||||
"next": "13.4.19",
|
||||
"next": "13.5.5",
|
||||
"next-auth": "4.23.2",
|
||||
"next-i18next": "13.3.0",
|
||||
"next-mdx-remote": "4.4.1",
|
||||
"nodemailer": "6.9.5",
|
||||
"nodemailer": "6.9.6",
|
||||
"raw-body": "2.5.2",
|
||||
"react": "18.2.0",
|
||||
"react-daisyui": "4.1.2",
|
||||
"react-dom": "18.2.0",
|
||||
"react-i18next": "12.3.1",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"remark-gfm": "4.0.0",
|
||||
"remark-gfm": "3.0.1",
|
||||
"sharp": "0.32.6",
|
||||
"swr": "2.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-cli": "4.0.4",
|
||||
"@playwright/test": "1.38.1",
|
||||
"@types/cors": "2.8.14",
|
||||
"@playwright/test": "1.39.0",
|
||||
"@types/cors": "2.8.15",
|
||||
"@types/micromatch": "4.0.3",
|
||||
"@types/node": "20.8.2",
|
||||
"@types/react": "18.2.25",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.4",
|
||||
"@typescript-eslint/parser": "6.7.4",
|
||||
"@types/node": "20.8.6",
|
||||
"@types/react": "18.2.28",
|
||||
"@typescript-eslint/eslint-plugin": "6.8.0",
|
||||
"@typescript-eslint/parser": "6.8.0",
|
||||
"autoprefixer": "10.4.16",
|
||||
"cross-env": "7.0.3",
|
||||
"env-cmd": "10.1.0",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-config-next": "13.5.4",
|
||||
"eslint": "8.51.0",
|
||||
"eslint-config-next": "13.5.5",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"postcss": "8.4.31",
|
||||
"prettier": "3.0.3",
|
||||
"prettier-plugin-tailwindcss": "0.5.5",
|
||||
"prettier-plugin-tailwindcss": "0.5.6",
|
||||
"release-it": "16.2.1",
|
||||
"swagger-jsdoc": "6.2.8",
|
||||
"tailwindcss": "3.3.3",
|
||||
|
|
|
@ -10,7 +10,7 @@ import micromatch from 'micromatch';
|
|||
import nextI18NextConfig from '../next-i18next.config.js';
|
||||
|
||||
import { AccountLayout, SetupLinkLayout } from '@components/layouts';
|
||||
|
||||
import '@boxyhq/react-ui/dist/style.css';
|
||||
import '../styles/globals.css';
|
||||
|
||||
const unauthenticatedRoutes = [
|
||||
|
|
|
@ -20,7 +20,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
};
|
||||
|
||||
// Handle SAML Response generated by IdP
|
||||
const { redirect_url, app_select_form, responseForm } = await oauthController.samlResponse({
|
||||
const { redirect_url, app_select_form, response_form } = await oauthController.samlResponse({
|
||||
SAMLResponse,
|
||||
RelayState,
|
||||
idp_hint,
|
||||
|
@ -36,9 +36,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
return res.send(app_select_form);
|
||||
}
|
||||
|
||||
if (responseForm) {
|
||||
if (response_form) {
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
return res.send(responseForm);
|
||||
return res.send(response_form);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const { message, statusCode = 500 } = error;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: skaffold/v4beta6
|
||||
kind: Config
|
||||
manifests:
|
||||
kustomize:
|
||||
paths:
|
||||
- ./kustomize/overlays/prod-eu/services
|
|
@ -0,0 +1,6 @@
|
|||
apiVersion: skaffold/v4beta6
|
||||
kind: Config
|
||||
manifests:
|
||||
kustomize:
|
||||
paths:
|
||||
- ./kustomize/overlays/prod-eu
|
|
@ -10,8 +10,18 @@ html,
|
|||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans,
|
||||
Droid Sans, Helvetica Neue, sans-serif;
|
||||
font-family:
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Segoe UI,
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
Fira Sans,
|
||||
Droid Sans,
|
||||
Helvetica Neue,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
},
|
||||
"host": "localhost:5225",
|
||||
"basePath": "/",
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"schemes": ["http", "https"],
|
||||
"securityDefinitions": {
|
||||
"apiKey": {
|
||||
"type": "apiKey",
|
||||
|
@ -38,16 +35,9 @@
|
|||
"post": {
|
||||
"summary": "Create SSO connection",
|
||||
"operationId": "create-sso-connection",
|
||||
"tags": [
|
||||
"Single Sign On"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Single Sign On"],
|
||||
"produces": ["application/json"],
|
||||
"consumes": ["application/x-www-form-urlencoded", "application/json"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/nameParamPost"
|
||||
|
@ -107,13 +97,8 @@
|
|||
"patch": {
|
||||
"summary": "Update SSO Connection",
|
||||
"operationId": "update-sso-connection",
|
||||
"tags": [
|
||||
"Single Sign On"
|
||||
],
|
||||
"consumes": [
|
||||
"application/json",
|
||||
"application/x-www-form-urlencoded"
|
||||
],
|
||||
"tags": ["Single Sign On"],
|
||||
"consumes": ["application/json", "application/x-www-form-urlencoded"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/clientIDParamPatch"
|
||||
|
@ -196,9 +181,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "get-connections",
|
||||
"tags": [
|
||||
"Single Sign On"
|
||||
],
|
||||
"tags": ["Single Sign On"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/200Get"
|
||||
|
@ -231,9 +214,7 @@
|
|||
],
|
||||
"summary": "Delete SSO Connections",
|
||||
"operationId": "delete-sso-connection",
|
||||
"tags": [
|
||||
"Single Sign On"
|
||||
],
|
||||
"tags": ["Single Sign On"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
|
@ -256,9 +237,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "get-connections-by-product",
|
||||
"tags": [
|
||||
"Single Sign On"
|
||||
],
|
||||
"tags": ["Single Sign On"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/responses/200Get"
|
||||
|
@ -276,12 +255,8 @@
|
|||
"post": {
|
||||
"summary": "Code exchange",
|
||||
"operationId": "oauth-code-exchange",
|
||||
"tags": [
|
||||
"OAuth"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded"
|
||||
],
|
||||
"tags": ["OAuth"],
|
||||
"consumes": ["application/x-www-form-urlencoded"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "grant_type",
|
||||
|
@ -356,9 +331,7 @@
|
|||
"get": {
|
||||
"summary": "Get profile",
|
||||
"operationId": "oauth-get-profile",
|
||||
"tags": [
|
||||
"OAuth"
|
||||
],
|
||||
"tags": ["OAuth"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -413,16 +386,9 @@
|
|||
"post": {
|
||||
"summary": "Create a Setup Link",
|
||||
"operationId": "create-sso-setup-link",
|
||||
"tags": [
|
||||
"Setup Links | Single Sign On"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Setup Links | Single Sign On"],
|
||||
"produces": ["application/json"],
|
||||
"consumes": ["application/x-www-form-urlencoded", "application/json"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/tenantParamPost"
|
||||
|
@ -460,9 +426,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "delete-sso-setup-link",
|
||||
"tags": [
|
||||
"Setup Links | Single Sign On"
|
||||
],
|
||||
"tags": ["Setup Links | Single Sign On"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -489,9 +453,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "get-sso-setup-link",
|
||||
"tags": [
|
||||
"Setup Links | Single Sign On"
|
||||
],
|
||||
"tags": ["Setup Links | Single Sign On"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -506,16 +468,9 @@
|
|||
"post": {
|
||||
"summary": "Create a Setup Link",
|
||||
"operationId": "create-dsync-setup-link",
|
||||
"tags": [
|
||||
"Setup Links | Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Setup Links | Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"consumes": ["application/x-www-form-urlencoded", "application/json"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/tenantParamPost"
|
||||
|
@ -547,9 +502,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "delete-dsync-setup-link",
|
||||
"tags": [
|
||||
"Setup Links | Directory Sync"
|
||||
],
|
||||
"tags": ["Setup Links | Directory Sync"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -576,9 +529,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "get-dsync-setup-link",
|
||||
"tags": [
|
||||
"Setup Links | Directory Sync"
|
||||
],
|
||||
"tags": ["Setup Links | Directory Sync"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -598,9 +549,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "get-sso-setup-link-by-product",
|
||||
"tags": [
|
||||
"Setup Links | Single Sign On"
|
||||
],
|
||||
"tags": ["Setup Links | Single Sign On"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -623,9 +572,7 @@
|
|||
}
|
||||
],
|
||||
"operationId": "get-dsync-setup-link-by-product",
|
||||
"tags": [
|
||||
"Setup Links | Directory Sync"
|
||||
],
|
||||
"tags": ["Setup Links | Directory Sync"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -651,12 +598,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Traces"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Traces"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -675,12 +618,8 @@
|
|||
"$ref": "#/parameters/product"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Traces"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Traces"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -741,16 +680,9 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"consumes": ["application/x-www-form-urlencoded", "application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -773,16 +705,9 @@
|
|||
"$ref": "#/parameters/product"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"consumes": ["application/x-www-form-urlencoded", "application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -808,12 +733,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -834,12 +755,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success"
|
||||
|
@ -855,12 +772,8 @@
|
|||
"$ref": "#/parameters/product"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -892,12 +805,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -922,12 +831,8 @@
|
|||
"$ref": "#/parameters/directoryId"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -959,12 +864,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -989,12 +890,8 @@
|
|||
"$ref": "#/parameters/directoryId"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Directory Sync"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["Directory Sync"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -1069,16 +966,9 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Federation"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"consumes": [
|
||||
"application/x-www-form-urlencoded",
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Federation"],
|
||||
"produces": ["application/json"],
|
||||
"consumes": ["application/x-www-form-urlencoded", "application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -1116,12 +1006,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Federation"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Federation"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -1198,12 +1084,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Federation"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Federation"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -1238,12 +1120,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Federation"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Federation"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -1266,12 +1144,8 @@
|
|||
"type": "string"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"SAML Federation"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": ["SAML Federation"],
|
||||
"produces": ["application/json"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
|
@ -1301,9 +1175,7 @@
|
|||
"provider": "okta.com"
|
||||
},
|
||||
"defaultRedirectUrl": "https://hoppscotch.io/",
|
||||
"redirectUrl": [
|
||||
"https://hoppscotch.io/"
|
||||
],
|
||||
"redirectUrl": ["https://hoppscotch.io/"],
|
||||
"tenant": "hoppscotch.io",
|
||||
"product": "API Engine",
|
||||
"name": "Hoppscotch-SP",
|
||||
|
@ -1899,4 +1771,4 @@
|
|||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue