Compare commits

..

34 Commits

Author SHA1 Message Date
58363c2c1d Revert "Move CI to Git submodule"
All checks were successful
ci/woodpecker/push/build/1 Pipeline was successful
ci/woodpecker/push/build/2 Pipeline was successful
ci/woodpecker/push/manifest Pipeline was successful
This reverts commit 86d0b37713.
2024-08-20 12:59:25 +03:00
86d0b37713 Move CI to Git submodule 2024-08-20 12:50:14 +03:00
de2c82617f Use mirror.gcr.io for prod Docker stage as well
All checks were successful
ci/woodpecker/push/build/1 Pipeline was successful
ci/woodpecker/push/build/2 Pipeline was successful
ci/woodpecker/push/manifest Pipeline was successful
2024-08-15 23:00:19 +03:00
f6e2278c81 Use mirror.gcr.io
Some checks failed
ci/woodpecker/push/build/1 Pipeline failed
ci/woodpecker/push/build/2 Pipeline failed
ci/woodpecker/push/manifest unknown status
2024-08-15 22:56:41 +03:00
f36f893e70 Update CI
Some checks failed
ci/woodpecker/push/build/2 Pipeline failed
ci/woodpecker/push/build/1 Pipeline was successful
ci/woodpecker/push/manifest unknown status
2024-08-15 22:49:30 +03:00
Sergo
63abd46364 chore(cve): update packages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-11 12:17:03 +03:00
6f465d79a3 Update config/prod.js
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-27 20:50:05 +00:00
78d2ec5ce6 Update config/prod.js
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-07-27 20:43:41 +00:00
6cecbec592 Revert "fix(oidc): temporarily disable pkce"
All checks were successful
continuous-integration/drone/push Build is passing
This reverts commit ed9559efe5.
2024-03-26 15:27:58 +02:00
ed9559efe5 fix(oidc): temporarily disable pkce
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:47:45 +02:00
4d17c3bcff fix(oidc): hardcode scopes
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:39:19 +02:00
d0e5faa040 fix(oidc): update oidc client
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:00:30 +02:00
4f76cce8d3 fix(oidc): fix oidc conf
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 13:48:14 +02:00
e7ad775573 fix(oidc): fix oidc conf
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 13:24:54 +02:00
5d9ca61ecc fix(ci): fix drone ci
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 13:04:59 +02:00
29480105c5 fix(oidc): get all oidc parameters from env
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is failing
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-03-25 15:10:26 +02:00
Sergo
caca4f3409 update readme
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-24 17:24:19 +02:00
Sergo
4b78256ae2 update readme, logger logs in json format
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-24 17:21:32 +02:00
Sergo
c6e74cca90 add card-style template for mobile view
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-02-04 01:48:14 +02:00
Sergo
1ca64bc925 render data with Handlebars template, support tags
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-04 00:07:01 +02:00
Sergo
b772c83c53 update bootstrap cdn
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-01 22:30:16 +02:00
76f1c43858 adj: prevent deleting non-managed addresses
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2023-08-13 19:23:38 +03:00
a2d344ceb4 minor fixes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-13 18:46:56 +03:00
50d07820dc add redis dependency
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-12 12:44:40 +03:00
7a41437121 rm redis pass
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-12 11:24:53 +03:00
049e90540d Merge branch 'master' into dev
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-12 11:18:07 +03:00
c93df5d573 Merge pull request 'store express sessions in redis, delete only aliases from preferred domain' (#3) from dev into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: k-space/walias#3
2023-08-12 08:14:48 +00:00
e8d1ba9c96 add redis creds
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-12 10:56:16 +03:00
c0a310b5ff update TODO
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-06 09:34:26 +03:00
d9014d2a54 merge master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-06 09:30:44 +03:00
feb5e5b4ca add prettier 2023-08-06 09:21:17 +03:00
01931cb238 Merge pull request 'dev-prettier' (#4) from dev-prettier into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: k-space/walias#4
2023-08-06 06:15:01 +00:00
cc453e2337 sanitize aliases response, hide delete button for non-prefereable domain aliases
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-05 15:02:44 +03:00
fcb5780ef9 store express sessions in redis
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-05 11:48:46 +03:00
21 changed files with 588 additions and 259 deletions

View File

@ -8,3 +8,4 @@ deployment.yaml
Dockerfile Dockerfile
readme.md readme.md
.git .git
lib

View File

@ -1,35 +0,0 @@
kind: pipeline
type: kubernetes
name: build
steps:
- name: docker
image: harbor.k-space.ee/k-space/drone-kaniko
settings:
repo: ${DRONE_REPO}
tags: ${DRONE_BRANCH}
registry: harbor.codemowers.eu
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: test
image: harbor.codemowers.eu/msergo/walias:${DRONE_COMMIT_SHA}
settings:
registry: harbor.codemowers.eu
username:
from_secret: docker_username
password:
from_secret: docker_password
commands:
- npm test
# services:
# - name: mysql
# image: mysql:8
# environment:
# MYSQL_ROOT_PASSWORD: dev
# MYSQL_DATABASE: sky
image_pull_secrets:
- dockerconfigjson

View File

@ -1,9 +1,9 @@
{ {
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 120, "printWidth": 120,
"quoteProps": "as-needed", "quoteProps": "as-needed",
"arrowParens": "avoid" "arrowParens": "avoid"
} }

View File

@ -1,13 +0,0 @@
pipeline:
build:
image: plugins/kaniko
settings:
repo: harbor.k-space.ee/${CI_REPO}
tags: latest
registry: harbor.k-space.ee
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
branch: master

26
.woodpecker/build.yaml Normal file
View File

@ -0,0 +1,26 @@
---
matrix:
ARCH:
- amd64
- arm64
steps:
- name: build
image: woodpeckerci/plugin-kaniko
backend_options:
kubernetes:
nodeSelector:
kubernetes.io/arch: ${ARCH}
tolerations:
- key: arch
operator: Equal
value: ${ARCH}
effect: NoSchedule
settings:
repo: ${CI_REPO}
registry: harbor.k-space.ee
tags: latest-${ARCH}
username:
from_secret: docker_username
password:
from_secret: docker_password

31
.woodpecker/manifest.yaml Normal file
View File

@ -0,0 +1,31 @@
---
skip_clone: true
steps:
- name: manifest
image: mirror.gcr.io/mplatform/manifest-tool:alpine-v2.1.6
secrets:
- docker_username
- docker_password
commands:
- set -u
- ls -lash
- env
- |
cat << EOF > spec.yaml
image: "harbor.k-space.ee/${CI_REPO}:latest"
manifests:
- image: "harbor.k-space.ee/${CI_REPO}:latest-amd64"
platform:
architecture: amd64
os: linux
- image: "harbor.k-space.ee/${CI_REPO}:latest-arm64"
platform:
architecture: arm64
os: linux
EOF
- /manifest-tool --username $docker_username --password $docker_password push from-spec spec.yaml > stdout
- cat stdout
depends_on:
- build

View File

@ -1,4 +1,4 @@
FROM node:18-alpine as dev FROM mirror.gcr.io/library/node:18-alpine as dev
RUN apk add netcat-openbsd RUN apk add netcat-openbsd
RUN npm config set update-notifier false RUN npm config set update-notifier false
@ -12,7 +12,7 @@ RUN npm run compile
ENTRYPOINT npm run start ENTRYPOINT npm run start
FROM node:18-alpine AS prod FROM mirror.gcr.io/node:18-alpine AS prod
RUN npm config set update-notifier false RUN npm config set update-notifier false
WORKDIR /app WORKDIR /app

View File

@ -5,6 +5,7 @@
"origins": [ "origins": [
"http://localhost:3030" "http://localhost:3030"
], ],
"sessionSecret": "test",
"paginate": { "paginate": {
"default": 10, "default": 10,
"max": 50 "max": 50
@ -14,5 +15,8 @@
"token": "aaaaa", "token": "aaaaa",
"domain": "test-codemowers.eu", "domain": "test-codemowers.eu",
"preferredDomain": "k-space.ee" "preferredDomain": "k-space.ee"
},
"redis": {
"url": "redis://localhost:6379"
} }
} }

View File

@ -1,14 +1,27 @@
module.exports = { module.exports = {
clientUrl: process.env.CLIENT_URL, clientUrl: process.env.CLIENT_URL,
sessionSecret: process.env.SESSION_SECRET,
oidc: { oidc: {
gatewayUri: process.env.OIDC_GATEWAY_URI, gatewayUri: process.env.OIDC_GATEWAY_URI,
gatewayAuthUri: process.env.OIDC_GATEWAY_AUTH_URI,
gatewayTokenUri: process.env.OIDC_GATEWAY_TOKEN_URI,
gatewayUserinfoUri: process.env.OIDC_GATEWAY_USERINFO_URI,
clientId: process.env.OIDC_CLIENT_ID, clientId: process.env.OIDC_CLIENT_ID,
clientSecret: process.env.OIDC_CLIENT_SECRET, clientSecret: process.env.OIDC_CLIENT_SECRET,
redirectUris: process.env.OIDC_REDIRECT_URIS redirectUris: process.env.OIDC_REDIRECT_URIS,
scopes: 'openid profile',
grantTypes: process.env.OIDC_GRANT_TYPES,
signedResponseAlg: process.env.OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG,
codeChallengeMethod: 'S256',
responseTypes: process.env.OIDC_RESPONSE_TYPES,
authMethod: process.env.OIDC_TOKEN_ENDPOINT_AUTH_METHOD,
}, },
wildDuck: { wildDuck: {
url: process.env.WILDDUCK_URL, url: process.env.WILDDUCK_URL,
token: process.env.WILDDUCK_TOKEN, token: process.env.WILDDUCK_TOKEN,
domain: process.env.WILDDUCK_DOMAIN domain: process.env.WILDDUCK_DOMAIN,
} },
redis: {
url: process.env.REDIS_URL,
},
}; };

View File

@ -98,6 +98,11 @@ spec:
secretKeyRef: secretKeyRef:
name: walias-secrets name: walias-secrets
key: WILDDUCK_DOMAIN key: WILDDUCK_DOMAIN
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-walias-cache-owner-secrets
key: REDIS_MASTER_URI
envFrom: envFrom:
- secretRef: - secretRef:
name: oidc-client-walias-owner-secrets name: oidc-client-walias-owner-secrets

334
package-lock.json generated
View File

@ -1,11 +1,11 @@
{ {
"name": "walias", "name": "wildflock",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "walias", "name": "wildflock",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@faker-js/faker": "^8.0.2", "@faker-js/faker": "^8.0.2",
@ -22,9 +22,12 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"config": "^3.3.9", "config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"openid-client": "^5.4.3", "ioredis": "^5.3.2",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0" "winston": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
@ -34,6 +37,7 @@
"@types/express-session": "^1.17.7", "@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^20.4.5", "@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.2.3", "lint-staged": "^13.2.3",
@ -413,6 +417,11 @@
"url": "https://github.com/sponsors/daffl" "url": "https://github.com/sponsors/daffl"
} }
}, },
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"node_modules/@jridgewell/resolve-uri": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
@ -438,6 +447,59 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"node_modules/@redis/bloom": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/client": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz",
"integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==",
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
"yallist": "4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@redis/graph": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/json": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/search": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz",
"integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/time-series": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@socket.io/component-emitter": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@ -610,6 +672,16 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
}, },
"node_modules/@types/redis": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz",
"integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==",
"deprecated": "This is a stub types definition. redis provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"redis": "*"
}
},
"node_modules/@types/send": { "node_modules/@types/send": {
"version": "0.17.1", "version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
@ -867,11 +939,11 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.4.0", "version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.4",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -931,12 +1003,12 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.1", "version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.4", "content-type": "~1.0.5",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"destroy": "1.2.0", "destroy": "1.2.0",
@ -944,7 +1016,7 @@
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.11.0",
"raw-body": "2.5.1", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
}, },
@ -971,12 +1043,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -1229,6 +1301,14 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/color": { "node_modules/color": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@ -1340,6 +1420,17 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/connect-redis": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-7.1.0.tgz",
"integrity": "sha512-UaqO1EirWjON2ENsyau7N5lbkrdYBpS6mYlXSeff/OYXsd6EGZ+SXSmNPoljL2PSua8fgjAEaldSA73PMZQ9Eg==",
"engines": {
"node": ">=16"
},
"peerDependencies": {
"express-session": ">=1"
}
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -1379,9 +1470,9 @@
} }
}, },
"node_modules/cookie": { "node_modules/cookie": {
"version": "0.5.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@ -1501,6 +1592,14 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1566,9 +1665,9 @@
} }
}, },
"node_modules/engine.io": { "node_modules/engine.io": {
"version": "6.5.1", "version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==", "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"dependencies": { "dependencies": {
"@types/cookie": "^0.4.1", "@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
@ -1578,17 +1677,17 @@
"cookie": "~0.4.1", "cookie": "~0.4.1",
"cors": "~2.8.5", "cors": "~2.8.5",
"debug": "~4.3.1", "debug": "~4.3.1",
"engine.io-parser": "~5.1.0", "engine.io-parser": "~5.2.1",
"ws": "~8.11.0" "ws": "~8.17.1"
}, },
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.2.0"
} }
}, },
"node_modules/engine.io-parser": { "node_modules/engine.io-parser": {
"version": "5.1.0", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
} }
@ -1724,16 +1823,16 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.18.2", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.1", "body-parser": "1.20.2",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.5.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@ -1868,9 +1967,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@ -1927,9 +2026,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.2", "version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -1999,6 +2098,14 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
}, },
"node_modules/generic-pool": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
"engines": {
"node": ">= 4"
}
},
"node_modules/get-caller-file": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -2274,6 +2381,50 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ioredis/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/ioredis/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -2398,9 +2549,9 @@
"dev": true "dev": true
}, },
"node_modules/jose": { "node_modules/jose": {
"version": "4.14.4", "version": "4.15.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
@ -2650,6 +2801,16 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -3232,11 +3393,11 @@
} }
}, },
"node_modules/openid-client": { "node_modules/openid-client": {
"version": "5.4.3", "version": "5.6.5",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
"integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==", "integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==",
"dependencies": { "dependencies": {
"jose": "^4.14.4", "jose": "^4.15.5",
"lru-cache": "^6.0.0", "lru-cache": "^6.0.0",
"object-hash": "^2.2.0", "object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3" "oidc-token-hash": "^5.0.3"
@ -3478,9 +3639,9 @@
} }
}, },
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "2.5.1", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
@ -3536,6 +3697,38 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/redis": {
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz",
"integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==",
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.5.8",
"@redis/graph": "1.1.0",
"@redis/json": "1.0.4",
"@redis/search": "1.1.3",
"@redis/time-series": "1.0.4"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -3851,13 +4044,35 @@
} }
}, },
"node_modules/socket.io-adapter": { "node_modules/socket.io-adapter": {
"version": "2.5.2", "version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"dependencies": { "dependencies": {
"ws": "~8.11.0" "debug": "~4.3.4",
"ws": "~8.17.1"
} }
}, },
"node_modules/socket.io-adapter/node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-adapter/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/socket.io-parser": { "node_modules/socket.io-parser": {
"version": "4.2.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
@ -3920,6 +4135,11 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -4357,15 +4577,15 @@
"dev": true "dev": true
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.11.0", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"bufferutil": "^4.0.1", "bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2" "utf-8-validate": ">=5.0.2"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"bufferutil": { "bufferutil": {

View File

@ -1,5 +1,5 @@
{ {
"name": "walias", "name": "wildflock",
"description": "Aliases for Wild Duck", "description": "Aliases for Wild Duck",
"version": "1.0.0", "version": "1.0.0",
"homepage": "", "homepage": "",
@ -7,9 +7,16 @@
"keywords": [ "keywords": [
"feathers" "feathers"
], ],
"author": { "authors": [
"url": "github.com/msergo" {
}, "name": "Sergii",
"url": "https://github.com/msergo"
},
{
"name": "Erki",
"url": "https://github.com/veebkolm"
}
],
"contributors": [], "contributors": [],
"bugs": {}, "bugs": {},
"engines": { "engines": {
@ -55,9 +62,12 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"config": "^3.3.9", "config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"openid-client": "^5.4.3", "ioredis": "^5.3.2",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0" "winston": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
@ -67,6 +77,7 @@
"@types/express-session": "^1.17.7", "@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^20.4.5", "@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.2.3", "lint-staged": "^13.2.3",

View File

@ -2,15 +2,16 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>walias</title> <title>WildDuck Aliases</title>
<script src="index.js" async></script>
<meta name="description" content="Aliases for Wild Duck"> <meta name="description" content="Aliases for Wild Duck">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous"> integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<script src="index.js" async></script>
<style> <style>
* { * {
margin: 0; margin: 0;
@ -50,11 +51,11 @@
</style> </style>
</head> </head>
<body > <body>
<div class="wrapper"> <div class="wrapper">
<div class="header"> <div class="header">
<img class="logo" src="logo.jpg"> <img class="logo" src="logo.jpg">
<h1>Wildduck aliases</h1> <h1>WildDuck Aliases</h1>
</div> </div>
<div id="container"></div> <div id="container"></div>
</div> </div>

View File

@ -1,93 +1,98 @@
function renderAliases(aliases) { function renderAliases(data) {
const dataContainer = document.getElementById('container') const dataContainer = document.getElementById('container');
// Create table element ant populate with items const template = window.innerWidth > 768 ? Handlebars.compile(defaultTemplate) : Handlebars.compile(responsiveCardTemplate);
const table = document.createElement('table') const html = template({
table.classList.add('table') data: data
const thead = document.createElement('thead') .sort((a, b) => new Date(a.created) - new Date(b.created))
const tr = document.createElement('tr') .map(alias => ({ ...alias, created: new Date(alias.created).toLocaleString() }))
});
for (const value of ['Address', 'Created']) { dataContainer.innerHTML = html;
const th = document.createElement('th')
th.setAttribute('scope', 'col')
th.innerText = value
tr.appendChild(th)
}
thead.appendChild(tr)
table.appendChild(thead)
const tbody = document.createElement('tbody')
// sort by field created
aliases
.sort((a, b) => new Date(a.created) - new Date(b.created))
.forEach(alias => {
const tr = document.createElement('tr')
const tdAddress = document.createElement('td')
// On click copy to clipboard
tdAddress.addEventListener('click', () => {
navigator.clipboard.writeText(alias.address)
})
tdAddress.innerText = alias.address
const tdCreated = document.createElement('td')
tdCreated.innerText = alias.created
const tdActions = document.createElement('td')
const aDelete = document.createElement('a')
aDelete.addEventListener('click', async () => {
const res = await fetch(`/aliases/${alias.id}`, {
method: 'DELETE'
})
if (res.ok) {
const data = await res.json()
renderAliases(data)
return
}
})
aDelete.classList.add('btn', 'btn-danger')
aDelete.innerText = 'Delete'
tdActions.appendChild(aDelete)
tr.appendChild(tdAddress)
tr.appendChild(tdCreated)
tr.appendChild(tdActions)
tbody.appendChild(tr)
})
table.appendChild(tbody)
dataContainer.innerHTML = ''
dataContainer.appendChild(table)
renderCreateButton();
} }
function renderCreateButton() { function createAlias() {
const dataContainer = document.getElementById('container') const tags = document.getElementById('tags').value?.split(',') || [];
const button = document.createElement('a')
button.classList.add('btn')
button.classList.add('btn-primary')
button.addEventListener('click', async () => { fetch('/aliases', {
button.innerText = 'Creating, please wait...' method: 'POST',
const res = await fetch('/aliases', { body: JSON.stringify({ tags }),
method: 'POST' headers: {
}) 'Content-Type': 'application/json'
if (res.ok) {
const data = await res.json()
renderAliases(data)
return
} }
}) })
.then(res => res.json())
button.innerText = 'Create new alias' .then(data => {
dataContainer.appendChild(button) renderAliases(data)
})
} }
function deleteAlias(id) {
fetch(`/aliases/${id}`, {
method: 'DELETE'
})
.then(res => res.json())
.then(data => {
renderAliases(data)
})
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
}
const defaultTemplate = `
<table class="table">
<thead>
<tr>
<th scope="col">Address</th>
<th scope="col">Tags</th>
<th scope="col">Created</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{{#each data}}
<tr>
<td onclick="copyToClipboard('{{address}}')" style="cursor: pointer;">{{address}}</td>
<td> {{#each tags}}{{this}} {{/each}}</td>
<td>{{created}}</td>
<td>
<div onclick="deleteAlias('{{id}}')" class="btn btn-danger {{#unless id}}disabled{{/unless}}">Delete</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
<input type="text" id="tags" placeholder="Add tags (optional)">
<div class="btn btn-primary" onclick="createAlias()">Create alias</div class="btn btn-primary">
`
// Responsive card template for mobile
const responsiveCardTemplate = `
{{#each data}}
<div class="card">
<div class="card-body">
<h5 class="card-title
{{#if tags.length}}
text-primary
{{/if}}
">
{{#each tags}}
<span class="badge bg-secondary">{{this}}</span>
{{/each}}
</h5>
<h5 class="card-title" onclick="copyToClipboard('{{address}}')" style="cursor: pointer;">{{address}}</h5>
<p class="card-text">{{created}}</p>
<div onclick="deleteAlias('{{id}}')" class="btn btn-sm btn-danger {{#unless id}}disabled{{/unless}}">Delete</div>
</div>
</div>
{{/each}}
<input type="text" id="tags" placeholder="Add tags (optional)">
<button class="btn btn-primary btn-sm" onclick="createAlias()">Create alias</button">
`
fetch('/aliases').then(async res => { fetch('/aliases').then(async res => {
const data = await res.json() const dataContainer = document.getElementById('container');
const dataContainer = document.getElementById('container') const data = await res.json();
if (!res.ok) { if (!res.ok) {
dataContainer.innerHTML = ` dataContainer.innerHTML = `

BIN
public/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,18 +1,19 @@
# walias # Wildflock
> Manage email aliases for Wild Duck mail server Headless service for managing address aliases in [Wild Duck](https://wildduck.email/) mail server.
## About ## About
This project was build for [K-Space Hackathon2023](https://wiki.k-space.ee/en/hackathon/2023) This project was built for [K-Space Hackathon 2023](https://wiki.k-space.ee/en/hackathon/2023) and is supposed to live in K-Space infrastructure; however, it can be seamlessly deployed elsewhere.
The main motivation behind the development of this headless web UI was to provide users with a convenient tool for creating and deleting email aliases in the Wild Duck mail server.
## Features The Wild Duck server has a nice and functional [API](https://docs.wildduck.email/api/), but only offers an admin API token, limiting regular users' ability to manage their own email aliases.
* auth with oidc
* headless only, no DB User authentication is implemented via OIDC.
* develop/debug with skaffold on k8s cluster
No persistent storage, Redis is used for keeping express sessions.
Deployed on Kubernetes, developed with Skaffold.
![Screenshot](./public/screenshot.png)
## TODO
* remove TS-related hacks
* add tests
* make nicer UI
* implement persistent sessions for multi-docker deployment (redis, db, etc)

View File

@ -1,19 +1,22 @@
import { randomUUID } from 'crypto';
import { feathers } from '@feathersjs/feathers'; import { feathers } from '@feathersjs/feathers';
import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express'; import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express';
import configuration from '@feathersjs/configuration'; import configuration from '@feathersjs/configuration';
import socketio from '@feathersjs/socketio'; import socketio from '@feathersjs/socketio';
import session from 'express-session'; import session from 'express-session';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
import config from 'config';
import type { Application } from './declarations'; import type { Application } from './declarations';
import { logger } from './logger'; import { logger } from './logger';
import { logError } from './hooks/log-error'; import { logError } from './hooks/log-error';
import { services } from './services/index'; import { services } from './services/index';
import { channels } from './channels'; import { channels } from './channels';
import { randomUUID } from 'crypto'; import { Env, getEnv } from './helpers/get-env';
const app: Application = express(feathers()); const app: Application = express(feathers());
let sessionStore;
// Load app configuration // Load app configuration
app.configure(configuration()); app.configure(configuration());
@ -25,11 +28,26 @@ app.use(
); );
app.use(cookieParser()); app.use(cookieParser());
if (getEnv() === Env.prod) {
const redisClient = createClient({
url: config.get('redis.url'),
});
sessionStore = new RedisStore({
prefix: 'walias:',
client: redisClient,
});
redisClient.connect().catch(console.error);
}
app.use( app.use(
session({ session({
secret: randomUUID(), store: sessionStore,
secret: config.get('sessionSecret') || randomUUID(),
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: false,
cookie: { secure: false }, cookie: { secure: false },
}) })
); );

16
src/helpers/get-env.ts Normal file
View File

@ -0,0 +1,16 @@
export enum Env {
dev = 'dev',
prod = 'prod',
test = 'test',
}
export const getEnv = (): Env => {
const env = process.env.NODE_ENV;
if (env === 'prod') {
return Env.prod;
} else if (env === 'test') {
return Env.test;
} else {
return Env.dev;
}
};

View File

@ -1,10 +1,7 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html
import { createLogger, format, transports } from 'winston'; import { createLogger, format, transports } from 'winston';
// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston
export const logger = createLogger({ export const logger = createLogger({
// To see more detailed errors, change this to 'debug'
level: 'info', level: 'info',
format: format.combine(format.splat(), format.simple()), format: format.combine(format.splat(), format.json()),
transports: [new transports.Console()], transports: [new transports.Console()],
}); });

View File

@ -6,7 +6,7 @@ import { faker } from '@faker-js/faker';
import { BadRequest } from '@feathersjs/errors'; import { BadRequest } from '@feathersjs/errors';
import config from 'config'; import config from 'config';
interface Alias { interface WildDuckAddress {
success: boolean; success: boolean;
id: string; id: string;
address: string; address: string;
@ -16,21 +16,27 @@ interface Alias {
created: string; created: string;
} }
interface GetAddressInfoResponse { interface GetWildDuckAddressInfoResponse {
success: boolean; success: boolean;
results: Alias[]; results: WildDuckAddress[];
} }
interface CreateAddressResponse { interface AliasApiResponse {
id: string | null;
address: string;
tags: string[];
created: string;
}
interface CreateWildDuckAddressResponse {
success: boolean; success: boolean;
id: string; id: string;
} }
type AliasesData = { tags?: string[] }
type AliasesData = any;
type AliasesPatch = any; type AliasesPatch = any;
type AliasesQuery = any; type AliasesQuery = any;
export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; export type { WildDuckAddress as Aliases, AliasesData, AliasesPatch, AliasesQuery };
export interface AliasesServiceOptions { export interface AliasesServiceOptions {
app: Application; app: Application;
@ -41,18 +47,18 @@ export interface AliasesParams extends Params<AliasesQuery> {
} }
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
implements ServiceInterface<Alias, AliasesData, ServiceParams, AliasesPatch> implements ServiceInterface<AliasApiResponse, AliasesData, ServiceParams, AliasesPatch>
{ {
constructor(public options: AliasesServiceOptions) {} constructor(public options: AliasesServiceOptions) { }
async find(params: ServiceParams): Promise<Alias[]> { async find(params: ServiceParams): Promise<AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params); const userId = await this.getUserIdByEmailAddress(params);
return this.getUserAddresses(userId); return this.getUserAddresses(userId);
} }
async create(data: AliasesData, params: ServiceParams): Promise<Alias>; async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse>;
async create(data: AliasesData, params: ServiceParams): Promise<Alias | Alias[]> { async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse | AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params); const userId = await this.getUserIdByEmailAddress(params);
const randomString = faker.git.commitSha({ length: 4 }); const randomString = faker.git.commitSha({ length: 4 });
@ -65,8 +71,9 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
const emailDomain = config.get('wildDuck.domain'); const emailDomain = config.get('wildDuck.domain');
const createResult = await wildDuckClient.post<CreateAddressResponse>(`/users/${userId}/addresses`, { const createResult = await wildDuckClient.post<CreateWildDuckAddressResponse>(`/users/${userId}/addresses`, {
address: `${alias}@${emailDomain}`, address: `${alias}@${emailDomain}`,
tags: data.tags,
}); });
if (!createResult.data.success) { if (!createResult.data.success) {
@ -79,25 +86,31 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> { private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> {
const emails = params.session?.user?.emails; const emails = params.session?.user?.emails;
const preferredDomain = config.get('wildDuck.preferredDomain');
if (!emails.length || !preferredDomain) {
throw new BadRequest('Unable to find user');
}
const addressInfoResponse = await Promise.any( const addressInfoResponse = await Promise.any(
emails emails
.filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain'))) .filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain')))
.map((email: string) => wildDuckClient.get<Alias>(`addresses/resolve/${email}`)) .map((email: string) => wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${email}`))
); );
return addressInfoResponse.data.user; return addressInfoResponse.data.user;
} }
private async getUserAddresses(userId: string): Promise<Alias[]> { private async getUserAddresses(userId: string): Promise<AliasApiResponse[]> {
const { data: userAddressesResponse } = await wildDuckClient.get<GetAddressInfoResponse>( const { data: userAddressesResponse } = await wildDuckClient.get<GetWildDuckAddressInfoResponse>(
`/users/${userId}/addresses` `/users/${userId}/addresses`
); );
return userAddressesResponse.results; return userAddressesResponse.results.map(this.sanitizeAliasResponse);
} }
async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> { async remove(id: NullableId, params: ServiceParams): Promise<AliasApiResponse[]> {
const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(`addresses/resolve/${id}`); const { data: addressInfoResponse } = await wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${id}`);
const allowedDomain: string = config.get('wildDuck.domain'); const allowedDomain: string = config.get('wildDuck.domain');
// If address does not match the allowed domain, throw an error // If address does not match the allowed domain, throw an error
@ -106,10 +119,23 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
} }
const userId = await this.getUserIdByEmailAddress(params); const userId = await this.getUserIdByEmailAddress(params);
await wildDuckClient.delete<Alias>(`users/${userId}/addresses/${id}`); await wildDuckClient.delete<WildDuckAddress>(`users/${userId}/addresses/${id}`);
return this.getUserAddresses(userId); return this.getUserAddresses(userId);
} }
sanitizeAliasResponse(alias: WildDuckAddress): AliasApiResponse {
// Prevent the user from deleting their main address or any address that does not end with the preferred domain
const isRemovable = !alias.main && alias.address.endsWith(config.get('wildDuck.domain'));
// Hide the id if the alias is not removable
return {
id: isRemovable ? alias.id : null,
address: alias.address,
tags: alias.tags,
created: alias.created,
};
}
} }
export const getOptions = (app: Application) => { export const getOptions = (app: Application) => {

View File

@ -29,17 +29,19 @@ export class AuthOidcService<ServiceParams extends AuthOidcParams = AuthOidcPara
client_id: config.get('oidc.clientId'), client_id: config.get('oidc.clientId'),
client_secret: config.get('oidc.clientSecret'), client_secret: config.get('oidc.clientSecret'),
redirect_uris: [config.get('oidc.redirectUris')], redirect_uris: [config.get('oidc.redirectUris')],
response_types: ['code'], response_types: [config.get('oidc.responseTypes')],
id_token_signed_response_alg: config.get('oidc.signedResponseAlg'),
token_endpoint_auth_method: config.get('oidc.authMethod'),
}); });
const codeVerifier = generators.codeVerifier(); const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier); const codeChallenge = generators.codeChallenge(codeVerifier);
const url = client.authorizationUrl({ const url = client.authorizationUrl({
redirect_uri: config.get('clientUrl') + '/auth-oidc/callback', redirect_uri: config.get('clientUrl') + '/auth-oidc/callback',
scope: 'openid profile offline_access', scope: config.get('oidc.scopes'),
response_type: 'code', response_type: config.get('oidc.responseTypes'),
code_challenge: codeChallenge, code_challenge: codeChallenge,
code_challenge_method: 'S256', code_challenge_method: config.get('oidc.codeChallengeMethod'),
}); });
params.session.codeVerifier = codeVerifier; params.session.codeVerifier = codeVerifier;