Compare commits

..

No commits in common. "master" and "dev-prettier" have entirely different histories.

21 changed files with 258 additions and 587 deletions

View File

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

35
.drone.yml Normal file
View File

@ -0,0 +1,35 @@
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",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"printWidth": 120,
"quoteProps": "as-needed",
"arrowParens": "avoid"
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"printWidth": 120,
"quoteProps": "as-needed",
"arrowParens": "avoid"
}

13
.woodpecker.yml Normal file
View File

@ -0,0 +1,13 @@
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

View File

@ -1,26 +0,0 @@
---
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

View File

@ -1,31 +0,0 @@
---
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 mirror.gcr.io/library/node:18-alpine as dev
FROM node:18-alpine as dev
RUN apk add netcat-openbsd
RUN npm config set update-notifier false
@ -12,7 +12,7 @@ RUN npm run compile
ENTRYPOINT npm run start
FROM mirror.gcr.io/node:18-alpine AS prod
FROM node:18-alpine AS prod
RUN npm config set update-notifier false
WORKDIR /app
@ -23,4 +23,4 @@ COPY --from=dev /app/public /app/public
RUN npm ci --only=production --silent
CMD ["npm", "start"]
CMD ["npm", "start"]

View File

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

View File

@ -1,27 +1,14 @@
module.exports = {
clientUrl: process.env.CLIENT_URL,
sessionSecret: process.env.SESSION_SECRET,
oidc: {
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,
clientSecret: process.env.OIDC_CLIENT_SECRET,
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,
redirectUris: process.env.OIDC_REDIRECT_URIS
},
wildDuck: {
url: process.env.WILDDUCK_URL,
token: process.env.WILDDUCK_TOKEN,
domain: process.env.WILDDUCK_DOMAIN,
},
redis: {
url: process.env.REDIS_URL,
},
};
domain: process.env.WILDDUCK_DOMAIN
}
};

View File

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

334
package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "wildflock",
"name": "walias",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "wildflock",
"name": "walias",
"version": "1.0.0",
"dependencies": {
"@faker-js/faker": "^8.0.2",
@ -22,12 +22,9 @@
"axios": "^1.4.0",
"compression": "^1.7.4",
"config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6",
"express-session": "^1.17.3",
"ioredis": "^5.3.2",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"openid-client": "^5.4.3",
"winston": "^3.10.0"
},
"devDependencies": {
@ -37,7 +34,6 @@
"@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1",
"@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
@ -417,11 +413,6 @@
"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": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
@ -447,59 +438,6 @@
"@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": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@ -672,16 +610,6 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"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": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
@ -939,11 +867,11 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": {
"follow-redirects": "^1.15.4",
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@ -1003,12 +931,12 @@
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@ -1016,7 +944,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.2",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@ -1043,12 +971,12 @@
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.1.1"
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
@ -1301,14 +1229,6 @@
"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": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@ -1420,17 +1340,6 @@
"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": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -1470,9 +1379,9 @@
}
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": {
"node": ">= 0.6"
}
@ -1592,14 +1501,6 @@
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1665,9 +1566,9 @@
}
},
"node_modules/engine.io": {
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz",
"integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
@ -1677,17 +1578,17 @@
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
"engine.io-parser": "~5.1.0",
"ws": "~8.11.0"
},
"engines": {
"node": ">=10.2.0"
"node": ">=10.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
"integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
"engines": {
"node": ">=10.0.0"
}
@ -1823,16 +1724,16 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.1",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.5.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@ -1967,9 +1868,9 @@
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@ -2026,9 +1927,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
@ -2098,14 +1999,6 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"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": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -2381,50 +2274,6 @@
"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": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -2549,9 +2398,9 @@
"dev": true
},
"node_modules/jose": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@ -2801,16 +2650,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"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": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -3393,11 +3232,11 @@
}
},
"node_modules/openid-client": {
"version": "5.6.5",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
"integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==",
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz",
"integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==",
"dependencies": {
"jose": "^4.15.5",
"jose": "^4.14.4",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
@ -3639,9 +3478,9 @@
}
},
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@ -3697,38 +3536,6 @@
"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": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -4044,35 +3851,13 @@
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
"ws": "~8.11.0"
}
},
"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": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
@ -4135,11 +3920,6 @@
"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": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -4577,15 +4357,15 @@
"dev": true
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {

View File

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

View File

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

View File

@ -1,98 +1,93 @@
function renderAliases(data) {
const dataContainer = document.getElementById('container');
const template = window.innerWidth > 768 ? Handlebars.compile(defaultTemplate) : Handlebars.compile(responsiveCardTemplate);
const html = template({
data: data
.sort((a, b) => new Date(a.created) - new Date(b.created))
.map(alias => ({ ...alias, created: new Date(alias.created).toLocaleString() }))
});
function renderAliases(aliases) {
const dataContainer = document.getElementById('container')
// Create table element ant populate with items
const table = document.createElement('table')
table.classList.add('table')
const thead = document.createElement('thead')
const tr = document.createElement('tr')
dataContainer.innerHTML = html;
for (const value of ['Address', 'Created']) {
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 createAlias() {
const tags = document.getElementById('tags').value?.split(',') || [];
function renderCreateButton() {
const dataContainer = document.getElementById('container')
const button = document.createElement('a')
button.classList.add('btn')
button.classList.add('btn-primary')
fetch('/aliases', {
method: 'POST',
body: JSON.stringify({ tags }),
headers: {
'Content-Type': 'application/json'
button.addEventListener('click', async () => {
button.innerText = 'Creating, please wait...'
const res = await fetch('/aliases', {
method: 'POST'
})
if (res.ok) {
const data = await res.json()
renderAliases(data)
return
}
})
.then(res => res.json())
.then(data => {
renderAliases(data)
})
}
function deleteAlias(id) {
fetch(`/aliases/${id}`, {
method: 'DELETE'
})
.then(res => res.json())
.then(data => {
renderAliases(data)
})
button.innerText = 'Create new alias'
dataContainer.appendChild(button)
}
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 => {
const dataContainer = document.getElementById('container');
const data = await res.json();
const data = await res.json()
const dataContainer = document.getElementById('container')
if (!res.ok) {
dataContainer.innerHTML = `

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,19 +1,18 @@
# Wildflock
# walias
Headless service for managing address aliases in [Wild Duck](https://wildduck.email/) mail server.
> Manage email aliases for Wild Duck mail server
## About
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.
This project was build for [K-Space Hackathon2023](https://wiki.k-space.ee/en/hackathon/2023)
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.
User authentication is implemented via OIDC.
No persistent storage, Redis is used for keeping express sessions.
Deployed on Kubernetes, developed with Skaffold.
![Screenshot](./public/screenshot.png)
## Features
* auth with oidc
* headless only, no DB
* develop/debug with skaffold on k8s cluster
## TODO
* remove TS-related hacks
* add tests
* make nicer UI
* implement persistent sessions for multi-docker deployment (redis, db, etc)

View File

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

View File

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

View File

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

View File

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