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 Dockerfile
readme.md readme.md
.git .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", "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"
} }

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 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 mirror.gcr.io/node:18-alpine AS prod FROM node:18-alpine AS prod
RUN npm config set update-notifier false RUN npm config set update-notifier false
WORKDIR /app WORKDIR /app
@ -23,4 +23,4 @@ COPY --from=dev /app/public /app/public
RUN npm ci --only=production --silent RUN npm ci --only=production --silent
CMD ["npm", "start"] CMD ["npm", "start"]

View File

@ -5,7 +5,6 @@
"origins": [ "origins": [
"http://localhost:3030" "http://localhost:3030"
], ],
"sessionSecret": "test",
"paginate": { "paginate": {
"default": 10, "default": 10,
"max": 50 "max": 50
@ -15,8 +14,5 @@
"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,27 +1,14 @@
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,11 +98,6 @@ 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": "wildflock", "name": "walias",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "wildflock", "name": "walias",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@faker-js/faker": "^8.0.2", "@faker-js/faker": "^8.0.2",
@ -22,12 +22,9 @@
"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",
"ioredis": "^5.3.2", "openid-client": "^5.4.3",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0" "winston": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
@ -37,7 +34,6 @@
"@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",
@ -417,11 +413,6 @@
"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",
@ -447,59 +438,6 @@
"@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",
@ -672,16 +610,6 @@
"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",
@ -939,11 +867,11 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.7", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.4", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@ -1003,12 +931,12 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"content-type": "~1.0.5", "content-type": "~1.0.4",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"destroy": "1.2.0", "destroy": "1.2.0",
@ -1016,7 +944,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.2", "raw-body": "2.5.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
}, },
@ -1043,12 +971,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.3", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.1.1" "fill-range": "^7.0.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -1301,14 +1229,6 @@
"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",
@ -1420,17 +1340,6 @@
"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",
@ -1470,9 +1379,9 @@
} }
}, },
"node_modules/cookie": { "node_modules/cookie": {
"version": "0.6.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
@ -1592,14 +1501,6 @@
"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",
@ -1665,9 +1566,9 @@
} }
}, },
"node_modules/engine.io": { "node_modules/engine.io": {
"version": "6.5.5", "version": "6.5.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==",
"dependencies": { "dependencies": {
"@types/cookie": "^0.4.1", "@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
@ -1677,17 +1578,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.2.1", "engine.io-parser": "~5.1.0",
"ws": "~8.17.1" "ws": "~8.11.0"
}, },
"engines": { "engines": {
"node": ">=10.2.0" "node": ">=10.0.0"
} }
}, },
"node_modules/engine.io-parser": { "node_modules/engine.io-parser": {
"version": "5.2.3", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
} }
@ -1823,16 +1724,16 @@
} }
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.1",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.5.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",
@ -1967,9 +1868,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@ -2026,9 +1927,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.6", "version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -2098,14 +1999,6 @@
"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",
@ -2381,50 +2274,6 @@
"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",
@ -2549,9 +2398,9 @@
"dev": true "dev": true
}, },
"node_modules/jose": { "node_modules/jose": {
"version": "4.15.5", "version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==",
"funding": { "funding": {
"url": "https://github.com/sponsors/panva" "url": "https://github.com/sponsors/panva"
} }
@ -2801,16 +2650,6 @@
"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",
@ -3393,11 +3232,11 @@
} }
}, },
"node_modules/openid-client": { "node_modules/openid-client": {
"version": "5.6.5", "version": "5.4.3",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz",
"integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==", "integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==",
"dependencies": { "dependencies": {
"jose": "^4.15.5", "jose": "^4.14.4",
"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"
@ -3639,9 +3478,9 @@
} }
}, },
"node_modules/raw-body": { "node_modules/raw-body": {
"version": "2.5.2", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
@ -3697,38 +3536,6 @@
"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",
@ -4044,35 +3851,13 @@
} }
}, },
"node_modules/socket.io-adapter": { "node_modules/socket.io-adapter": {
"version": "2.5.5", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"dependencies": { "dependencies": {
"debug": "~4.3.4", "ws": "~8.11.0"
"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",
@ -4135,11 +3920,6 @@
"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",
@ -4577,15 +4357,15 @@
"dev": true "dev": true
}, },
"node_modules/ws": { "node_modules/ws": {
"version": "8.17.1", "version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"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": "wildflock", "name": "walias",
"description": "Aliases for Wild Duck", "description": "Aliases for Wild Duck",
"version": "1.0.0", "version": "1.0.0",
"homepage": "", "homepage": "",
@ -7,16 +7,9 @@
"keywords": [ "keywords": [
"feathers" "feathers"
], ],
"authors": [ "author": {
{ "url": "github.com/msergo"
"name": "Sergii", },
"url": "https://github.com/msergo"
},
{
"name": "Erki",
"url": "https://github.com/veebkolm"
}
],
"contributors": [], "contributors": [],
"bugs": {}, "bugs": {},
"engines": { "engines": {
@ -62,12 +55,9 @@
"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",
"ioredis": "^5.3.2", "openid-client": "^5.4.3",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0" "winston": "^3.10.0"
}, },
"devDependencies": { "devDependencies": {
@ -77,7 +67,6 @@
"@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,16 +2,15 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>WildDuck Aliases</title> <title>walias</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.2/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
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;
@ -51,14 +50,14 @@
</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>
</body> </body>
</html> </html>

View File

@ -1,98 +1,93 @@
function renderAliases(data) { function renderAliases(aliases) {
const dataContainer = document.getElementById('container'); const dataContainer = document.getElementById('container')
const template = window.innerWidth > 768 ? Handlebars.compile(defaultTemplate) : Handlebars.compile(responsiveCardTemplate); // Create table element ant populate with items
const html = template({ const table = document.createElement('table')
data: data table.classList.add('table')
.sort((a, b) => new Date(a.created) - new Date(b.created)) const thead = document.createElement('thead')
.map(alias => ({ ...alias, created: new Date(alias.created).toLocaleString() })) 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() { function renderCreateButton() {
const tags = document.getElementById('tags').value?.split(',') || []; const dataContainer = document.getElementById('container')
const button = document.createElement('a')
button.classList.add('btn')
button.classList.add('btn-primary')
fetch('/aliases', { button.addEventListener('click', async () => {
method: 'POST', button.innerText = 'Creating, please wait...'
body: JSON.stringify({ tags }), const res = await fetch('/aliases', {
headers: { method: 'POST'
'Content-Type': 'application/json' })
if (res.ok) {
const data = await res.json()
renderAliases(data)
return
} }
}) })
.then(res => res.json())
.then(data => {
renderAliases(data)
})
}
function deleteAlias(id) { button.innerText = 'Create new alias'
fetch(`/aliases/${id}`, { dataContainer.appendChild(button)
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 dataContainer = document.getElementById('container'); const data = await res.json()
const data = await res.json(); const dataContainer = document.getElementById('container')
if (!res.ok) { if (!res.ok) {
dataContainer.innerHTML = ` 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 ## 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. This project was build for [K-Space Hackathon2023](https://wiki.k-space.ee/en/hackathon/2023)
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.
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. ## Features
* auth with oidc
User authentication is implemented via OIDC. * headless only, no DB
* 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,22 +1,19 @@
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 { Env, getEnv } from './helpers/get-env'; import { randomUUID } from 'crypto';
const app: Application = express(feathers()); const app: Application = express(feathers());
let sessionStore;
// Load app configuration // Load app configuration
app.configure(configuration()); app.configure(configuration());
@ -28,26 +25,11 @@ 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({
store: sessionStore, secret: randomUUID(),
secret: config.get('sessionSecret') || randomUUID(),
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: true,
cookie: { secure: false }, 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'; 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.json()), format: format.combine(format.splat(), format.simple()),
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 WildDuckAddress { interface Alias {
success: boolean; success: boolean;
id: string; id: string;
address: string; address: string;
@ -16,27 +16,21 @@ interface WildDuckAddress {
created: string; created: string;
} }
interface GetWildDuckAddressInfoResponse { interface GetAddressInfoResponse {
success: boolean; success: boolean;
results: WildDuckAddress[]; results: Alias[];
} }
interface AliasApiResponse { interface CreateAddressResponse {
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 { WildDuckAddress as Aliases, AliasesData, AliasesPatch, AliasesQuery }; export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery };
export interface AliasesServiceOptions { export interface AliasesServiceOptions {
app: Application; app: Application;
@ -47,18 +41,18 @@ export interface AliasesParams extends Params<AliasesQuery> {
} }
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> 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); const userId = await this.getUserIdByEmailAddress(params);
return this.getUserAddresses(userId); return this.getUserAddresses(userId);
} }
async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse>; async create(data: AliasesData, params: ServiceParams): Promise<Alias>;
async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse | AliasApiResponse[]> { async create(data: AliasesData, params: ServiceParams): Promise<Alias | Alias[]> {
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 });
@ -71,9 +65,8 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
const emailDomain = config.get('wildDuck.domain'); 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}`, address: `${alias}@${emailDomain}`,
tags: data.tags,
}); });
if (!createResult.data.success) { if (!createResult.data.success) {
@ -86,31 +79,25 @@ 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<WildDuckAddress>(`addresses/resolve/${email}`)) .map((email: string) => wildDuckClient.get<Alias>(`addresses/resolve/${email}`))
); );
return addressInfoResponse.data.user; return addressInfoResponse.data.user;
} }
private async getUserAddresses(userId: string): Promise<AliasApiResponse[]> { private async getUserAddresses(userId: string): Promise<Alias[]> {
const { data: userAddressesResponse } = await wildDuckClient.get<GetWildDuckAddressInfoResponse>( const { data: userAddressesResponse } = await wildDuckClient.get<GetAddressInfoResponse>(
`/users/${userId}/addresses` `/users/${userId}/addresses`
); );
return userAddressesResponse.results.map(this.sanitizeAliasResponse); return userAddressesResponse.results;
} }
async remove(id: NullableId, params: ServiceParams): Promise<AliasApiResponse[]> { async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> {
const { data: addressInfoResponse } = await wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${id}`); const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(`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
@ -119,23 +106,10 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
} }
const userId = await this.getUserIdByEmailAddress(params); 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); 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,19 +29,17 @@ 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: [config.get('oidc.responseTypes')], response_types: ['code'],
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: config.get('oidc.scopes'), scope: 'openid profile offline_access',
response_type: config.get('oidc.responseTypes'), response_type: 'code',
code_challenge: codeChallenge, code_challenge: codeChallenge,
code_challenge_method: config.get('oidc.codeChallengeMethod'), code_challenge_method: 'S256',
}); });
params.session.codeVerifier = codeVerifier; params.session.codeVerifier = codeVerifier;