initial
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
name: Docker (tags)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags-ignore:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
|
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
||||||
|
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm and npmci
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Audit production dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --prod
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Audit development dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --dev
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Test stable
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm test
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
npmci npm prepare
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci command npm run build
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
name: Docker (tags)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: code.foss.global/hosttoday/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{gitea.repository}}.git
|
||||||
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
|
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
||||||
|
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Audit production dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --prod
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Audit development dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --dev
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Test stable
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm test
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci command npm run build
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: test
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: code.foss.global/hosttoday/ht-docker-dbase:npmci
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @ship.zone/npmci
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
run: |
|
||||||
|
npmci docker login
|
||||||
|
npmci docker build
|
||||||
|
npmci docker test
|
||||||
|
# npmci docker push
|
||||||
|
npmci docker push
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
needs: test
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Trigger
|
||||||
|
run: npmci trigger
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
.nogit/
|
||||||
|
|
||||||
|
# artifacts
|
||||||
|
coverage/
|
||||||
|
public/
|
||||||
|
pages/
|
||||||
|
|
||||||
|
# installs
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.yarn/
|
||||||
|
.cache/
|
||||||
|
.rpt2_cache
|
||||||
|
|
||||||
|
# builds
|
||||||
|
dist/
|
||||||
|
dist_*/
|
||||||
|
|
||||||
|
# custom
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "npm test",
|
||||||
|
"name": "Run npm test",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": ["/npmextra.json"],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"npmci": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "settings for npmci"
|
||||||
|
},
|
||||||
|
"gitzone": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "settings for gitzone",
|
||||||
|
"properties": {
|
||||||
|
"projectType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# gitzone dockerfile_service
|
||||||
|
## STAGE 1 // BUILD
|
||||||
|
FROM code.foss.global/hosttoday/ht-docker-node:npmci as node1
|
||||||
|
COPY ./ /app
|
||||||
|
WORKDIR /app
|
||||||
|
ARG NPMCI_TOKEN_NPM2
|
||||||
|
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
|
||||||
|
RUN npmci npm prepare
|
||||||
|
RUN pnpm config set store-dir .pnpm-store
|
||||||
|
RUN rm -rf node_modules && pnpm install
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# gitzone dockerfile_service
|
||||||
|
## STAGE 2 // install production
|
||||||
|
FROM code.foss.global/hosttoday/ht-docker-node:npmci as node2
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=node1 /app /app
|
||||||
|
RUN rm -rf .pnpm-store
|
||||||
|
ARG NPMCI_TOKEN_NPM2
|
||||||
|
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
|
||||||
|
RUN npmci npm prepare
|
||||||
|
RUN pnpm config set store-dir .pnpm-store
|
||||||
|
RUN rm -rf node_modules/ && pnpm install --prod
|
||||||
|
|
||||||
|
|
||||||
|
## STAGE 3 // rebuild dependencies for alpine
|
||||||
|
FROM code.foss.global/hosttoday/ht-docker-node:alpinenpmci as node3
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=node2 /app /app
|
||||||
|
ARG NPMCI_TOKEN_NPM2
|
||||||
|
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
|
||||||
|
RUN npmci npm prepare
|
||||||
|
RUN pnpm config set store-dir .pnpm-store
|
||||||
|
RUN pnpm rebuild -r
|
||||||
|
|
||||||
|
## STAGE 4 // the final production image with all dependencies in place
|
||||||
|
FROM code.foss.global/hosttoday/ht-docker-node:alpine as node4
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=node3 /app /app
|
||||||
|
|
||||||
|
### Healthchecks
|
||||||
|
RUN pnpm install -g @servezone/healthy
|
||||||
|
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["npm", "start"]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
process.env.CLI_CALL = 'true';
|
||||||
|
import * as cliTool from './ts/index.js';
|
||||||
|
cliTool.runCli();
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
process.env.CLI_CALL = 'true';
|
||||||
|
const cliTool = await import('./dist_ts/index.js');
|
||||||
|
cliTool.runCli();
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
process.env.CLI_CALL = 'true';
|
||||||
|
|
||||||
|
import * as tsrun from '@git.zone/tsrun';
|
||||||
|
tsrun.runPath('./cli.child.js', import.meta.url);
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!--Lets set some basic meta tags-->
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="user-scalable=0, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height"
|
||||||
|
/>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
|
||||||
|
<!--Lets make sure we recognize this as an PWA-->
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<link rel="icon" type="image/png" href="/assetbroker/manifest/favicon.png" />
|
||||||
|
|
||||||
|
<!--Lets load standard fonts-->
|
||||||
|
<link rel="preconnect" href="https://assetbroker.lossless.one/" crossorigin>
|
||||||
|
<link rel="stylesheet" href="https://assetbroker.lossless.one/fonts/fonts.css">
|
||||||
|
|
||||||
|
|
||||||
|
<!--Lets avoid a rescaling flicker due to default body margins-->
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
position: relative;
|
||||||
|
background: #000;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
projectVersion = '';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #303f9f;
|
||||||
|
font-family: Inter, Roboto, sans-serif;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 600px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #4357d9;
|
||||||
|
}
|
||||||
|
.contentHeader {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 25px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="container">
|
||||||
|
<div class="contentHeader">We need JavaScript to run properly!</div>
|
||||||
|
<div class="content">
|
||||||
|
This site is being built using lit-element (made by Google). This technology works with
|
||||||
|
JavaScript. Subsequently this website does not work as intended without
|
||||||
|
JavaScript.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<a href="https://">Legal Info</a> |
|
||||||
|
<a href="https:///privacy">Privacy Policy</a>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
<script type="text/javascript" async defer>
|
||||||
|
window.revenueEnabled = true;
|
||||||
|
const runRevenueCheck = async () => {
|
||||||
|
var e = document.createElement('div');
|
||||||
|
e.id = '476kjuhzgtr764';
|
||||||
|
e.style.display = 'none';
|
||||||
|
document.body.appendChild(e);
|
||||||
|
if (document.getElementById('476kjuhzgtr764')) {
|
||||||
|
window.revenueEnabled = true;
|
||||||
|
} else {
|
||||||
|
window.revenueEnabled = false;
|
||||||
|
}
|
||||||
|
console.log(`revenue enabled: ${window.revenueEnabled}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
runRevenueCheck();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
<script defer type="module" src="/bundle.js"></script>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"gitzone": {
|
||||||
|
"projectType": "website",
|
||||||
|
"module": {
|
||||||
|
"githost": "code.foss.global",
|
||||||
|
"gitscope": "idp.global",
|
||||||
|
"gitrepo": "idp.global",
|
||||||
|
"description": "the code that runs the idp.global software",
|
||||||
|
"npmPackagename": "@idp.global/idp.global",
|
||||||
|
"license": "MIT",
|
||||||
|
"projectDomain": "idp.global"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npmci": {
|
||||||
|
"npmGlobalTools": [],
|
||||||
|
"dockerRegistryRepoMap": {
|
||||||
|
"registry.gitlab.com": "code.foss.global/idp.global/idp.global"
|
||||||
|
},
|
||||||
|
"dockerBuildargEnvMap": {
|
||||||
|
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
|
||||||
|
},
|
||||||
|
"npmRegistryUrl": "registry.npmjs.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"name": "@idp.global/idp.global",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "the code that runs the idp.global software",
|
||||||
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"test": "npm run build",
|
||||||
|
"build": "tsbuild tsfolders --web --allowimplicitany && tsbundle website --production",
|
||||||
|
"watch": "tswatch website",
|
||||||
|
"start": "(node cli.js)",
|
||||||
|
"startTs": "(node cli.ts.js)",
|
||||||
|
"buildDocs": "tsdoc"
|
||||||
|
},
|
||||||
|
"author": "Task Venture Capital GmbH",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@api.global/typedrequest": "^3.0.32",
|
||||||
|
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||||
|
"@api.global/typedserver": "^3.0.51",
|
||||||
|
"@api.global/typedsocket": "^3.0.1",
|
||||||
|
"@consentsoftware_private/catalog": "^1.0.73",
|
||||||
|
"@design.estate/dees-catalog": "^1.1.8",
|
||||||
|
"@design.estate/dees-domtools": "^2.0.23",
|
||||||
|
"@design.estate/dees-element": "^2.0.15",
|
||||||
|
"@push.rocks/lik": "^6.0.15",
|
||||||
|
"@push.rocks/qenv": "^6.0.5",
|
||||||
|
"@push.rocks/smartdata": "^5.2.10",
|
||||||
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
|
"@push.rocks/smarthash": "^3.0.4",
|
||||||
|
"@push.rocks/smartjson": "^5.0.20",
|
||||||
|
"@push.rocks/smartjwt": "^2.2.1",
|
||||||
|
"@push.rocks/smartlog": "^3.0.7",
|
||||||
|
"@push.rocks/smartmail": "^1.0.24",
|
||||||
|
"@push.rocks/smartpath": "^5.0.5",
|
||||||
|
"@push.rocks/smartpromise": "^4.0.4",
|
||||||
|
"@push.rocks/smartrx": "^3.0.7",
|
||||||
|
"@push.rocks/smartstate": "^2.0.0",
|
||||||
|
"@push.rocks/smarttime": "^4.0.8",
|
||||||
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
|
"@push.rocks/smarturl": "^3.0.7",
|
||||||
|
"@push.rocks/taskbuffer": "^3.1.7",
|
||||||
|
"@push.rocks/webjwt": "^1.0.9",
|
||||||
|
"@push.rocks/websetup": "^3.0.15",
|
||||||
|
"@push.rocks/webstore": "^2.0.20",
|
||||||
|
"@serve.zone/platformclient": "^1.0.6",
|
||||||
|
"@tsclass/tsclass": "^4.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@git.zone/tsbuild": "^2.1.17",
|
||||||
|
"@git.zone/tsbundle": "^2.0.3",
|
||||||
|
"@git.zone/tsrun": "^1.2.8",
|
||||||
|
"@git.zone/tswatch": "^2.0.1",
|
||||||
|
"@push.rocks/projectinfo": "^5.0.1",
|
||||||
|
"@types/node": "^22.7.2"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://code.foss.global/idp.global/idp.global.git"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://code.foss.global/idp.global/idp.global/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://code.foss.global/idp.global/idp.global#readme",
|
||||||
|
"browserslist": [
|
||||||
|
"last 1 chrome versions"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"ts_web/**/*",
|
||||||
|
"dist/**/*",
|
||||||
|
"dist_*/**/*",
|
||||||
|
"dist_ts/**/*",
|
||||||
|
"dist_ts_web/**/*",
|
||||||
|
"assets/**/*",
|
||||||
|
"cli.js",
|
||||||
|
"npmextra.json",
|
||||||
|
"readme.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+8171
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
|||||||
|
# Project Readme Hints
|
||||||
|
|
||||||
|
This is the initial readme hints file.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* autocreated commitinfo by @pushrocks/commitinfo
|
||||||
|
*/
|
||||||
|
export const commitinfo = {
|
||||||
|
name: '@idp.global/idp.global',
|
||||||
|
version: 'x.x.x',
|
||||||
|
description: 'website for lossless.com'
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import * as plugins from './ffb.plugins.js';
|
||||||
|
|
||||||
|
export const packageDir = plugins.path.join(
|
||||||
|
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||||
|
'../',
|
||||||
|
);
|
||||||
|
export const distWebDir = plugins.path.join(packageDir, 'dist_serve/');
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// native scope
|
||||||
|
import * as path from 'path';
|
||||||
|
export { path };
|
||||||
|
|
||||||
|
// @api.global scope
|
||||||
|
import * as typedserver from '@api.global/typedserver';
|
||||||
|
|
||||||
|
export { typedserver };
|
||||||
|
|
||||||
|
// @pushrocks scope
|
||||||
|
import * as qenv from '@push.rocks/qenv';
|
||||||
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
|
|
||||||
|
export { qenv, smartpath };
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
import * as plugins from './ffb.plugins.js';
|
||||||
|
import * as paths from './ffb.paths.js';
|
||||||
|
|
||||||
|
export const runCli = async () => {
|
||||||
|
const serviceQenv = new plugins.qenv.Qenv('./', './.nogit', false);
|
||||||
|
const websiteServer = new plugins.typedserver.utilityservers.UtilityWebsiteServer({
|
||||||
|
feedMetadata: null,
|
||||||
|
domain: 'idp.global',
|
||||||
|
serveDir: paths.distWebDir,
|
||||||
|
});
|
||||||
|
await websiteServer.start();
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { ApiTokenManager } from './classes.apitokenmanager.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
@plugins.smartdata.Manager(() => {
|
||||||
|
return (this as any).manager;
|
||||||
|
})
|
||||||
|
export class ApiToken extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
ApiToken,
|
||||||
|
ApiToken,
|
||||||
|
ApiTokenManager
|
||||||
|
> {
|
||||||
|
static clearOldApiTokens() {}
|
||||||
|
|
||||||
|
static clearApiTokensForUserId(userId: string) {}
|
||||||
|
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
public ownerEntityId: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
data: {
|
||||||
|
token: string;
|
||||||
|
scopes: string[];
|
||||||
|
} = {
|
||||||
|
token: null,
|
||||||
|
scopes: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class ApiTokenManager {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { BillingPlanManager } from './classes.billingplanmanager.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a billing plan belongs to a user which can then attribute the billing plan to a organization
|
||||||
|
*/
|
||||||
|
@plugins.smartdata.Manager()
|
||||||
|
export class BillingPlan extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
BillingPlan,
|
||||||
|
plugins.lointReception.data.IBillingPlan,
|
||||||
|
BillingPlanManager
|
||||||
|
> {
|
||||||
|
// STATIC
|
||||||
|
public static syncForUser(userArg: User) {
|
||||||
|
// TODO sync this for user
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public data: plugins.lointReception.data.IBillingPlan['data'] = {
|
||||||
|
type: null,
|
||||||
|
organizationId: null,
|
||||||
|
lastProcessed: null,
|
||||||
|
seats: null,
|
||||||
|
status: null,
|
||||||
|
billingEvents: [],
|
||||||
|
communications: [],
|
||||||
|
nextBilling: null,
|
||||||
|
proEnabled: false,
|
||||||
|
alternativePaymentData: null,
|
||||||
|
paddleData: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { BillingPlan } from './classes.billingplan.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class BillingPlanManager {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
public CBillingPlan = plugins.smartdata.setDefaultManagerForDoc(this, BillingPlan);
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
this.typedrouter.addTypedHandler(new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_UpdatePaymentMethod>('updatePaymentMethod', async reqDataArg => {
|
||||||
|
const user = await this.receptionRef.userManager.getUserByJwt(reqDataArg.jwtString);
|
||||||
|
const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({
|
||||||
|
id: reqDataArg.orgId,
|
||||||
|
});
|
||||||
|
const userIsAdmin = await organization.checkIfUserIsAdmin(user);
|
||||||
|
if (!userIsAdmin) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('user is not an admin for the organization that the billing plan is for');
|
||||||
|
}
|
||||||
|
// ok user is admin
|
||||||
|
const newBillingPlan = new this.CBillingPlan();
|
||||||
|
newBillingPlan.id = plugins.smartunique.shortId();
|
||||||
|
newBillingPlan.data = {
|
||||||
|
type: 'Paddle',
|
||||||
|
proEnabled: false,
|
||||||
|
organizationId: reqDataArg.orgId,
|
||||||
|
status: 'active',
|
||||||
|
seats: 0,
|
||||||
|
alternativePaymentData: null,
|
||||||
|
billingEvents: [],
|
||||||
|
communications: [],
|
||||||
|
lastProcessed: Date.now(),
|
||||||
|
nextBilling: {
|
||||||
|
items: [],
|
||||||
|
method: 'paddle',
|
||||||
|
ontrack: true,
|
||||||
|
selectedBillingDate: Date.now(),
|
||||||
|
},
|
||||||
|
paddleData: {
|
||||||
|
checkoutId: reqDataArg.paddle?.checkoutId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await newBillingPlan.save();
|
||||||
|
return {
|
||||||
|
billingPlan: {
|
||||||
|
id: newBillingPlan.id,
|
||||||
|
data: {
|
||||||
|
type: newBillingPlan.data.type,
|
||||||
|
organizationId: newBillingPlan.data.organizationId,
|
||||||
|
proEnabled: newBillingPlan.data.proEnabled,
|
||||||
|
nextBilling: newBillingPlan.data.nextBilling,
|
||||||
|
billingEvents: newBillingPlan.data.billingEvents,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { logger } from './logging.js';
|
||||||
|
|
||||||
|
export class ReceptionHousekeeping {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public taskmanager = new plugins.taskbuffer.TaskManager();
|
||||||
|
|
||||||
|
constructor(receptionArg: Reception) {
|
||||||
|
this.receptionRef = receptionArg;
|
||||||
|
|
||||||
|
// lets care about old loginsessions
|
||||||
|
this.taskmanager.addAndScheduleTask(
|
||||||
|
new plugins.taskbuffer.Task({
|
||||||
|
name: 'oldLoginSessions',
|
||||||
|
taskFunction: async () => {
|
||||||
|
logger.log('info', 'running login sessions cleaning task');
|
||||||
|
const oneWeekBeforeTimestamp =
|
||||||
|
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ weeks: 1 });
|
||||||
|
const oldLoginSessions =
|
||||||
|
await this.receptionRef.loginSessionManager.CLoginSession.getInstances({
|
||||||
|
data: {
|
||||||
|
validUntil: {
|
||||||
|
$lt: oneWeekBeforeTimestamp,
|
||||||
|
} as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
for (const loginSession of oldLoginSessions) {
|
||||||
|
await loginSession.delete();
|
||||||
|
}
|
||||||
|
logger.log('info', `Completed deletion of ${oldLoginSessions.length} old loginSessions`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'2 * * * * *'
|
||||||
|
);
|
||||||
|
|
||||||
|
this.taskmanager.start();
|
||||||
|
logger.log('info', 'housekeeping started');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop() {
|
||||||
|
this.taskmanager.stop();
|
||||||
|
logger.log('info', 'housekeeping stopped');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { JwtManager } from './classes.jwtmanager.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a User is identified by its username or email.
|
||||||
|
* Both need to be unique and both can be changed.
|
||||||
|
*/
|
||||||
|
@plugins.smartdata.Manager()
|
||||||
|
export class Jwt extends plugins.smartdata.SmartDataDbDoc<Jwt, plugins.lointReception.data.IJwt, JwtManager> {
|
||||||
|
// STATIC
|
||||||
|
public static async createJwtForRefreshToken(
|
||||||
|
jwtManagerInstance: JwtManager,
|
||||||
|
refreshTokenArg: string
|
||||||
|
) {
|
||||||
|
const loginSession =
|
||||||
|
await jwtManagerInstance.receptionRef.loginSessionManager.CLoginSession.getLoginSessionByRefreshToken(
|
||||||
|
refreshTokenArg
|
||||||
|
);
|
||||||
|
if (!loginSession) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const refreshTokenValid = await loginSession.validateRefreshToken(refreshTokenArg);
|
||||||
|
if (!refreshTokenValid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const user = await jwtManagerInstance.receptionRef.userManager.CUser.getInstance({
|
||||||
|
id: loginSession.data.userId,
|
||||||
|
});
|
||||||
|
const validUntil = plugins.smarttime.ExtendedDate.fromMillis(
|
||||||
|
Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 1 })
|
||||||
|
);
|
||||||
|
const jwt = new Jwt();
|
||||||
|
jwt.id = plugins.smartunique.shortId();
|
||||||
|
jwt.data = {
|
||||||
|
userId: user.id,
|
||||||
|
validUntil: validUntil.getTime(),
|
||||||
|
refreshEvery: 1000000,
|
||||||
|
refreshFrom: Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ days: 0.5 }),
|
||||||
|
refreshToken: await loginSession.getRefreshToken(), // TODO: handle multiple refresh tokens
|
||||||
|
justForLooks: {
|
||||||
|
validUntilIsoString: validUntil.toISOString(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await jwt.save();
|
||||||
|
|
||||||
|
const jwtString = await jwtManagerInstance.smartjwtInstance.createJWT({
|
||||||
|
id: jwt.id,
|
||||||
|
blocked: null,
|
||||||
|
data: jwt.data,
|
||||||
|
} as plugins.lointReception.data.IJwt);
|
||||||
|
return jwtString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public blocked: boolean = false;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public data: plugins.lointReception.data.IJwt['data'];
|
||||||
|
|
||||||
|
public async block() {
|
||||||
|
this.blocked = true;
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getLoginSession() {
|
||||||
|
const loginSession = await this.manager.receptionRef.loginSessionManager.CLoginSession.getInstance({
|
||||||
|
data: {
|
||||||
|
refreshToken: this.data.refreshToken,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return loginSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { Jwt } from './classes.jwt.js';
|
||||||
|
|
||||||
|
export class JwtManager {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
||||||
|
public jwtManagerEasyStore: plugins.smartdata.EasyStore<{
|
||||||
|
jwtJsonKeypair: plugins.tsclass.network.IJwtKeypair;
|
||||||
|
}>;
|
||||||
|
public blockedJwtIdList: string[] = [];
|
||||||
|
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
public CJwt = plugins.smartdata.setDefaultManagerForDoc(this, Jwt);
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
this.typedrouter.addTypedHandler<plugins.lointReception.request.IReq_RefreshJwt>(
|
||||||
|
new plugins.typedrequest.TypedHandler(
|
||||||
|
'refreshJwt',
|
||||||
|
async (requestArg) => {
|
||||||
|
const resultJwt = await Jwt.createJwtForRefreshToken(this, requestArg.refreshToken);
|
||||||
|
return {
|
||||||
|
status: 'loggedIn',
|
||||||
|
jwt: resultJwt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_GetPublicKeyForValidation>(
|
||||||
|
'getPublicKeyForValidation',
|
||||||
|
async (requestArg) => {
|
||||||
|
// TODO control backend token
|
||||||
|
return {
|
||||||
|
publicKeyPem: this.smartjwtInstance.getKeyPairAsJson().publicPem,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_PushOrGetJwtIdBlocklist>(
|
||||||
|
'pushOrGetJwtIdBlocklist',
|
||||||
|
async (requestArg) => {
|
||||||
|
// TODO control backend token
|
||||||
|
return {
|
||||||
|
blockedJwtIds: this.blockedJwtIdList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pushPublicKeyToClients() {
|
||||||
|
const targetConnections =
|
||||||
|
await this.receptionRef.serviceServer.typedsocket.findAllTargetConnectionsByTag<plugins.lointReception.tags.ITag_LolePubapi>(
|
||||||
|
'lole-reception',
|
||||||
|
{
|
||||||
|
backendToken: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
for (const targetConnection of targetConnections) {
|
||||||
|
const pushPublicKeyTr =
|
||||||
|
this.receptionRef.serviceServer.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_PushPublicKeyForValidation>(
|
||||||
|
'pushPublicKeyForValidation',
|
||||||
|
targetConnection
|
||||||
|
);
|
||||||
|
await pushPublicKeyTr.fire({
|
||||||
|
publicKeyPem: this.smartjwtInstance.getKeyPairAsJson().publicPem,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pushBlockedJwtIdListToClients() {
|
||||||
|
const targetConnections =
|
||||||
|
await this.receptionRef.serviceServer.typedsocket.findAllTargetConnectionsByTag<plugins.lointReception.tags.ITag_LolePubapi>(
|
||||||
|
'lole-reception',
|
||||||
|
{
|
||||||
|
backendToken: '',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
for (const targetConnection of targetConnections) {
|
||||||
|
const pushPublicKeyTr =
|
||||||
|
this.receptionRef.serviceServer.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_PushOrGetJwtIdBlocklist>(
|
||||||
|
'pushOrGetJwtIdBlocklist',
|
||||||
|
targetConnection
|
||||||
|
);
|
||||||
|
await pushPublicKeyTr.fire({
|
||||||
|
blockedJwtIds: this.blockedJwtIdList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
this.jwtManagerEasyStore = await this.receptionRef.db.smartdataDb.createEasyStore(
|
||||||
|
'jwtManagerEasyStore'
|
||||||
|
);
|
||||||
|
await this.smartjwtInstance.init();
|
||||||
|
let existingKeyPair = await this.jwtManagerEasyStore.readKey('jwtJsonKeypair');
|
||||||
|
if (!existingKeyPair) {
|
||||||
|
await this.rotateKeyPair();
|
||||||
|
}
|
||||||
|
existingKeyPair = await this.jwtManagerEasyStore.readKey('jwtJsonKeypair');
|
||||||
|
this.smartjwtInstance.setKeyPairAsJson(existingKeyPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async rotateKeyPair() {
|
||||||
|
await this.smartjwtInstance.createNewKeyPair();
|
||||||
|
await this.jwtManagerEasyStore.writeKey(
|
||||||
|
'jwtJsonKeypair',
|
||||||
|
this.smartjwtInstance.getKeyPairAsJson()
|
||||||
|
);
|
||||||
|
await this.pushPublicKeyToClients();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async verifyJWTAndGetData(jwtArg: string): Promise<Jwt> {
|
||||||
|
const jwtData: plugins.lointReception.data.IJwt = await this.smartjwtInstance.verifyJWTAndGetData(jwtArg);
|
||||||
|
const jwt = await Jwt.getInstance({
|
||||||
|
id: jwtData.id,
|
||||||
|
});
|
||||||
|
if (jwt.blocked) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (jwt) {
|
||||||
|
const loginSession = await jwt.getLoginSession();
|
||||||
|
if (!loginSession) {
|
||||||
|
await jwt.block();
|
||||||
|
this.blockedJwtIdList.push(jwt.id);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { LoginSessionManager } from './classes.loginsessionmanager.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a LoginSession keeps track of a login over the whole time of the user being loggedin
|
||||||
|
*/
|
||||||
|
@plugins.smartdata.Manager()
|
||||||
|
export class LoginSession extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
LoginSession,
|
||||||
|
plugins.lointReception.data.ILoginSession,
|
||||||
|
LoginSessionManager
|
||||||
|
> {
|
||||||
|
// ======
|
||||||
|
// static
|
||||||
|
// ======
|
||||||
|
public static async createLoginSessionForUser(userArg: User, deleteOtherSessions = false) {
|
||||||
|
const loginSession = new LoginSession();
|
||||||
|
loginSession.id = plugins.smartunique.shortId();
|
||||||
|
loginSession.data.userId = userArg.id;
|
||||||
|
await loginSession.save();
|
||||||
|
return loginSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async clearLoginSessionsForUser(userArg: User) {
|
||||||
|
// lets find existing sessions
|
||||||
|
const existingSessions = await LoginSession.getInstances({
|
||||||
|
id: userArg.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const existingSession of existingSessions) {
|
||||||
|
await existingSession.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getLoginSessionBySessionId(sessionIdArg: string) {
|
||||||
|
return await LoginSession.getInstance({
|
||||||
|
id: sessionIdArg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getLoginSessionByRefreshToken(refreshTokenArg: string) {
|
||||||
|
const loginSession = await LoginSession.getInstance({
|
||||||
|
data: {
|
||||||
|
refreshToken: refreshTokenArg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return loginSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========
|
||||||
|
// INSTANCE
|
||||||
|
// ========
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public data: plugins.lointReception.data.ILoginSession['data'] = {
|
||||||
|
userId: null,
|
||||||
|
validUntil: Date.now() + plugins.smarttime.getMilliSecondsFromUnits({ weeks: 1 }),
|
||||||
|
invalidated: false,
|
||||||
|
refreshToken: null,
|
||||||
|
deviceId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
public transferToken: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* invalidates a session
|
||||||
|
*/
|
||||||
|
public async invalidate() {
|
||||||
|
this.data.invalidated = true;
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a refresh token is unique to a login session and ONLY created once per login session
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async getRefreshToken() {
|
||||||
|
if (this.data.invalidated) {
|
||||||
|
console.log('login session is invalidated. no refresh token can be generated.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!this.data.refreshToken) {
|
||||||
|
this.data.refreshToken = plugins.smartunique.uni('refresh_');
|
||||||
|
}
|
||||||
|
await this.save();
|
||||||
|
return this.data.refreshToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTransferToken() {
|
||||||
|
this.transferToken = plugins.smartunique.uni('transfer_');
|
||||||
|
return this.transferToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validateRefreshToken(refreshTokenArg: string) {
|
||||||
|
return this.data.refreshToken === refreshTokenArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async validateTransferToken(transferTokenArg: string) {
|
||||||
|
const result = this.transferToken === transferTokenArg;
|
||||||
|
|
||||||
|
// a transfer token can only be used once, so we invalidate it here
|
||||||
|
if (result) {
|
||||||
|
this.transferToken = null;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { LoginSession } from './classes.loginsession.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
|
||||||
|
export class LoginSessionManager {
|
||||||
|
// refs
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CLoginSession = plugins.smartdata.setDefaultManagerForDoc(this, LoginSession);
|
||||||
|
|
||||||
|
public loginSessions = new plugins.lik.ObjectMap<LoginSession>();
|
||||||
|
|
||||||
|
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
public emailTokenMap = new plugins.lik.ObjectMap<{
|
||||||
|
email: string;
|
||||||
|
token: string;
|
||||||
|
action: 'emailLogin' | 'passwordReset';
|
||||||
|
}>();
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
this.receptionRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_LoginWithEmailOrUsernameAndPassword>(
|
||||||
|
'loginWithEmailOrUsernameAndPassword',
|
||||||
|
async (requestData) => {
|
||||||
|
let user = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
data: {
|
||||||
|
username: requestData.username,
|
||||||
|
passwordHash: await this.receptionRef.userManager.CUser.hashPassword(
|
||||||
|
requestData.password
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user && requestData.username.includes('@')) {
|
||||||
|
user = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
data: {
|
||||||
|
email: requestData.username,
|
||||||
|
passwordHash: await this.receptionRef.userManager.CUser.hashPassword(
|
||||||
|
requestData.password
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
// lets recheck
|
||||||
|
if (
|
||||||
|
(user.data.username !== requestData.username &&
|
||||||
|
user.data.email !== requestData.username) ||
|
||||||
|
user.data.passwordHash !==
|
||||||
|
(await this.receptionRef.userManager.CUser.hashPassword(requestData.password))
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'database returned a user that does not match wanted criterea. CRITICAL!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginSession = await LoginSession.createLoginSessionForUser(user);
|
||||||
|
this.loginSessions.add(loginSession);
|
||||||
|
const refreshToken = await loginSession.getRefreshToken();
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
twoFaNeeded: false,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('User not found!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_LoginWithEmail>(
|
||||||
|
'loginWithEmail',
|
||||||
|
async (requestDataArg) => {
|
||||||
|
const existingUser = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
data: {
|
||||||
|
email: requestDataArg.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (existingUser) {
|
||||||
|
this.emailTokenMap.findOneAndRemoveSync(
|
||||||
|
(itemArg) => itemArg.email === existingUser.data.email
|
||||||
|
);
|
||||||
|
const loginEmailToken = plugins.smartunique.uuid4();
|
||||||
|
this.emailTokenMap.add({
|
||||||
|
email: existingUser.data.email,
|
||||||
|
token: loginEmailToken,
|
||||||
|
action: 'emailLogin',
|
||||||
|
});
|
||||||
|
// lets make sure its only valid for 10 minutes
|
||||||
|
plugins.smartdelay.delayFor(600000, null, true).then(() => {
|
||||||
|
this.emailTokenMap.findOneAndRemoveSync(
|
||||||
|
(itemArg) => itemArg.token === loginEmailToken
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.receptionRef.receptionMailer.sendLoginWithEMailMail(existingUser, loginEmailToken);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
testOnlyToken: process.env.TEST_MODE
|
||||||
|
? this.emailTokenMap.findSync((itemArg) => itemArg.email === existingUser.data.email)
|
||||||
|
.token
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_LoginWithEmailAfterEmailTokenAquired>(
|
||||||
|
'loginWithEmailAfterEmailTokenAquired',
|
||||||
|
async (requestArg) => {
|
||||||
|
const tokenObject = this.emailTokenMap.findSync((itemArg) => {
|
||||||
|
return itemArg.email === requestArg.email && itemArg.token === requestArg.token;
|
||||||
|
});
|
||||||
|
if (tokenObject) {
|
||||||
|
const user = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
data: {
|
||||||
|
email: requestArg.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const loginSession = await LoginSession.createLoginSessionForUser(user);
|
||||||
|
this.loginSessions.add(loginSession);
|
||||||
|
return {
|
||||||
|
refreshToken: await loginSession.getRefreshToken(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Validation Token not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler<plugins.lointReception.request.ILogoutRequest>(
|
||||||
|
new plugins.typedrequest.TypedHandler('logout', async (requestDataArg) => {
|
||||||
|
const loginSession = await this.CLoginSession.getLoginSessionByRefreshToken(requestDataArg.refreshToken);
|
||||||
|
await loginSession.invalidate();
|
||||||
|
return {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
|
||||||
|
new plugins.typedrequest.TypedHandler(
|
||||||
|
'exchangeRefreshTokenAndTransferToken',
|
||||||
|
async (requestDataArg) => {
|
||||||
|
switch (true) {
|
||||||
|
case !!requestDataArg.refreshToken:
|
||||||
|
const loginSession = await this.loginSessions.find(async (loginSessionArg) => {
|
||||||
|
return loginSessionArg.validateRefreshToken(requestDataArg.refreshToken);
|
||||||
|
});
|
||||||
|
if (!loginSession) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('your refresh token is invalid');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
transferToken: await loginSession.getTransferToken(),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case !!requestDataArg.transferToken:
|
||||||
|
let transferToken: string;
|
||||||
|
const loginSession2 = await this.loginSessions.find(async (loginSessionArg) => {
|
||||||
|
return loginSessionArg.validateTransferToken(requestDataArg.transferToken);
|
||||||
|
});
|
||||||
|
if (!loginSession2) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'Your transfer token is not valid.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
refreshToken: await loginSession2.getRefreshToken(),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_ResetPassword>(
|
||||||
|
'resetPassword',
|
||||||
|
async (requestDataArg) => {
|
||||||
|
const emailOfPasswordToReset = requestDataArg.email;
|
||||||
|
const existingUser = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
data: {
|
||||||
|
email: emailOfPasswordToReset,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (existingUser) {
|
||||||
|
this.emailTokenMap.findOneAndRemoveSync(
|
||||||
|
(itemArg) => itemArg.email === existingUser.data.email
|
||||||
|
);
|
||||||
|
this.emailTokenMap.add({
|
||||||
|
email: existingUser.data.email,
|
||||||
|
token: plugins.smartunique.shortId(),
|
||||||
|
action: 'passwordReset',
|
||||||
|
});
|
||||||
|
plugins.smartdelay.delayFor(600000, null, true).then(() => {
|
||||||
|
this.emailTokenMap.findOneAndRemoveSync(
|
||||||
|
(itemArg) => itemArg.email === existingUser.data.email
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.receptionRef.receptionMailer.sendPasswordResetMail(
|
||||||
|
existingUser,
|
||||||
|
this.emailTokenMap.findSync((itemArg) => itemArg.email === existingUser.data.email)
|
||||||
|
.token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// note: we always return ok here, since we don't want to give any indication as to wether a user is already registered with us.
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_SetNewPassword>(
|
||||||
|
'setNewPassword',
|
||||||
|
async (requestData) => {
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a device id by simply returning a uuid4
|
||||||
|
*/
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_ObtainDeviceId>('obtainDeviceId', async (reqData) => {
|
||||||
|
reqData;
|
||||||
|
return {
|
||||||
|
deviceId: {
|
||||||
|
id: plugins.smartunique.uuid4()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_AttachDeviceId>('attachDeviceId', async (reqData) => {
|
||||||
|
// TODO: Blocked by proper JWT handling
|
||||||
|
reqData.jwt;
|
||||||
|
return {
|
||||||
|
ok: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { OrganizationManager } from './classes.organizationmanager.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
|
@plugins.smartdata.Manager()
|
||||||
|
export class Organization extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
Organization,
|
||||||
|
plugins.lointReception.data.IOrganization,
|
||||||
|
OrganizationManager
|
||||||
|
> {
|
||||||
|
public static async createNewOrganizationForUser(
|
||||||
|
organizationManagerArg: OrganizationManager,
|
||||||
|
userIdArg: string,
|
||||||
|
orgNameArg: string,
|
||||||
|
slugNameArg: string,
|
||||||
|
) {
|
||||||
|
const newOrg = new Organization();
|
||||||
|
newOrg.id = plugins.smartunique.shortId();
|
||||||
|
newOrg.data = {
|
||||||
|
name: orgNameArg,
|
||||||
|
slug: slugNameArg,
|
||||||
|
billingPlanId: null,
|
||||||
|
roleIds: [],
|
||||||
|
}
|
||||||
|
await newOrg.save();
|
||||||
|
return newOrg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
id: plugins.lointReception.data.IOrganization['id'];
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
data: plugins.lointReception.data.IOrganization['data'];
|
||||||
|
|
||||||
|
public async checkIfUserIsAdmin(userArg: User) {
|
||||||
|
const role = await this.manager.receptionRef.roleManager.getRoleForUserAndOrg(userArg, this);
|
||||||
|
return role.data.role === 'admin';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { Organization } from './classes.organization.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
|
export class OrganizationManager {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
public COrganization = plugins.smartdata.setDefaultManagerForDoc(this, Organization);
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_CreateOrganization>(
|
||||||
|
'createOrganization',
|
||||||
|
async (requestArg) => {
|
||||||
|
const nameIsAvailable = async () => {
|
||||||
|
const existingOrg = await this.COrganization.getInstance({
|
||||||
|
data: {
|
||||||
|
slug: requestArg.organizationSlug,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const nameAvailable = !existingOrg;
|
||||||
|
return nameAvailable;
|
||||||
|
};
|
||||||
|
switch (requestArg.action) {
|
||||||
|
case 'checkAvailability':
|
||||||
|
return {
|
||||||
|
nameAvailable: await nameIsAvailable(),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'manifest':
|
||||||
|
const nameCheckedOk = await nameIsAvailable();
|
||||||
|
const userData = await this.receptionRef.userManager.getUserByJwtValidation(
|
||||||
|
requestArg.jwt
|
||||||
|
);
|
||||||
|
const newOrg = await this.COrganization.createNewOrganizationForUser(
|
||||||
|
this,
|
||||||
|
userData.id,
|
||||||
|
requestArg.organizationName,
|
||||||
|
requestArg.organizationSlug
|
||||||
|
);
|
||||||
|
const role = await this.receptionRef.roleManager.modifyRoleForUserAtOrg({
|
||||||
|
action: 'create',
|
||||||
|
organizationId: newOrg.id,
|
||||||
|
userId: userData.id,
|
||||||
|
role: 'admin',
|
||||||
|
});
|
||||||
|
newOrg.data.roleIds.push(role.id);
|
||||||
|
await newOrg.save();
|
||||||
|
return {
|
||||||
|
nameAvailable: true,
|
||||||
|
resultingOrganization: await newOrg.createSavableObject()
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_GetOrganizationById>(
|
||||||
|
'getOrganizationById',
|
||||||
|
async (requestArg) => {
|
||||||
|
const verifiedJwt = await this.receptionRef.jwtManager.verifyJWTAndGetData(
|
||||||
|
requestArg.jwt
|
||||||
|
);
|
||||||
|
const user = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
id: verifiedJwt.data.userId,
|
||||||
|
});
|
||||||
|
const organization = await this.COrganization.getInstance({
|
||||||
|
id: requestArg.id
|
||||||
|
});
|
||||||
|
const role = await this.receptionRef.roleManager.CRole.getInstance({
|
||||||
|
data: {
|
||||||
|
organizationId: organization.id,
|
||||||
|
userId: user.id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('User not authorized for the requested organization.');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
organization: await organization.createSavableObject()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAllOrganizationsForUser(
|
||||||
|
userArg: User,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const organizations: Organization[] = [];
|
||||||
|
const userRoles = await this.receptionRef.roleManager.getAllRolesForUser(userArg);
|
||||||
|
|
||||||
|
for (const role of userRoles) {
|
||||||
|
const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({
|
||||||
|
id: role.data.organizationId
|
||||||
|
});
|
||||||
|
if (!organizations.find(orgArg => orgArg.id === organization.id)) {
|
||||||
|
organizations.push(organization);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return organizations;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as paths from './paths.js';
|
||||||
|
import { logger } from './logging.js';
|
||||||
|
|
||||||
|
import { JwtManager } from './classes.jwtmanager.js';
|
||||||
|
import { LoginSessionManager } from './classes.loginsessionmanager.js';
|
||||||
|
import { RegistrationSessionManager } from './classes.registrationsessionmanager.js';
|
||||||
|
import { ReceptionServer } from './classes.receptionserver.js';
|
||||||
|
import { ReceptionDb } from './classes.receptiondb.js';
|
||||||
|
import { ReceptionMailer } from './classes.receptionmailer.js';
|
||||||
|
import { UserManager } from './classes.usermanager.js';
|
||||||
|
import { ApiTokenManager } from './classes.apitokenmanager.js';
|
||||||
|
import { ReceptionHousekeeping } from './classes.housekeeping.js';
|
||||||
|
import { OrganizationManager } from './classes.organizationmanager.js';
|
||||||
|
import { RoleManager } from './classes.rolemanager.js';
|
||||||
|
import { BillingPlanManager } from './classes.billingplanmanager.js';
|
||||||
|
|
||||||
|
export class Reception {
|
||||||
|
public projectinfoNpm = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
public serviceQenv = new plugins.qenv.Qenv('./', './.nogit');
|
||||||
|
public szPlatformClient = new plugins.szPlatformClient.SzPlatformClient();
|
||||||
|
public db = new ReceptionDb(this);
|
||||||
|
|
||||||
|
// server
|
||||||
|
public serviceServer = new ReceptionServer(this);
|
||||||
|
|
||||||
|
// managers
|
||||||
|
public jwtManager = new JwtManager(this);
|
||||||
|
public loginSessionManager = new LoginSessionManager(this);
|
||||||
|
public registrationSessionManager = new RegistrationSessionManager(this);
|
||||||
|
public apitokenManager = new ApiTokenManager(this);
|
||||||
|
public receptionMailer = new ReceptionMailer(this);
|
||||||
|
public userManager = new UserManager(this);
|
||||||
|
public organizationmanager = new OrganizationManager(this);
|
||||||
|
public roleManager = new RoleManager(this);
|
||||||
|
public billingPlanManager = new BillingPlanManager(this);
|
||||||
|
housekeeping = new ReceptionHousekeeping(this);
|
||||||
|
|
||||||
|
constructor(public databaseName?: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* starts the reception instance
|
||||||
|
*/
|
||||||
|
public async start() {
|
||||||
|
logger.log('info', 'starting reception');
|
||||||
|
await this.db.start(this.databaseName);
|
||||||
|
await this.jwtManager.start();
|
||||||
|
await this.serviceServer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stops the reception instance
|
||||||
|
*/
|
||||||
|
public async stop() {
|
||||||
|
await this.housekeeping.stop();
|
||||||
|
await this.serviceServer.stop();
|
||||||
|
console.log('stopped serviceserver!');
|
||||||
|
await this.db.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
|
||||||
|
export class ReceptionDb {
|
||||||
|
public smartdataDb: plugins.smartdata.SmartdataDb;
|
||||||
|
public receptionRef: Reception;
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(databaseNameArg?: string) {
|
||||||
|
this.smartdataDb = new plugins.smartdata.SmartdataDb({
|
||||||
|
mongoDbUser: await this.receptionRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_USER'),
|
||||||
|
mongoDbName: databaseNameArg || await this.receptionRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_NAME'),
|
||||||
|
mongoDbPass: await this.receptionRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_PASS'),
|
||||||
|
mongoDbUrl: await this.receptionRef.serviceQenv.getEnvVarOnDemand('MONGO_DB_URL'),
|
||||||
|
});
|
||||||
|
await this.smartdataDb.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop() {
|
||||||
|
await this.smartdataDb.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { RegistrationSession } from './classes.registrationsession.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class ReceptionMailer {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBodyString = (textArg) => `
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
* {
|
||||||
|
font-family:Arial,Helvetica Neue,Helvetica,sans-serif;
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.bodyspacer {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
padding-top:20px;
|
||||||
|
padding-bottom:20px;
|
||||||
|
padding-left:10px;
|
||||||
|
padding-right:10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width: 200px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoBottom {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
width: 150px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align:center;
|
||||||
|
line-height:35px;
|
||||||
|
margin-bottom:20;
|
||||||
|
}
|
||||||
|
h1.subheading{
|
||||||
|
font-size:15px;
|
||||||
|
font-weight:normal;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
margin-bottom:5px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top:5px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
text-decoration:none;
|
||||||
|
color: #CCCCCC;
|
||||||
|
}
|
||||||
|
.contentspacer{
|
||||||
|
padding-left:20px;
|
||||||
|
padding-right:20px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
box-shadow: 0px 0px 3px rgba(0,0,0,0.5);
|
||||||
|
color: #333333;
|
||||||
|
background:#ffffff;
|
||||||
|
max-width:600px;
|
||||||
|
border-radius:3px;
|
||||||
|
margin-left:auto;
|
||||||
|
margin-right:auto;
|
||||||
|
min-height:40px;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.headerimagewrapper {
|
||||||
|
overflow:hidden;
|
||||||
|
border:none;
|
||||||
|
}
|
||||||
|
.headerimage {
|
||||||
|
min-height: 10px;
|
||||||
|
width:100%;
|
||||||
|
vertical-align:middle;
|
||||||
|
background:#eeeeeb;
|
||||||
|
}
|
||||||
|
img, a {
|
||||||
|
border:none;
|
||||||
|
outline:none;
|
||||||
|
}
|
||||||
|
.textcontent {
|
||||||
|
padding:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
width: 200px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #333333;
|
||||||
|
background: #f0f0f0;
|
||||||
|
margin: 20px auto;
|
||||||
|
padding: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
color: #fff;
|
||||||
|
background: #e4002b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
color: #CCCCCC;
|
||||||
|
text-align:center;
|
||||||
|
font-size:12px;
|
||||||
|
margin-top:10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="bodyspacer">
|
||||||
|
<img class="logo" src="https://assetbroker.lossless.one/brandfiles/00general/brightdark_workspaceglobal@2x.png" />
|
||||||
|
<div class="contentspacer">
|
||||||
|
<div class="content">
|
||||||
|
<div class="headerimagewrapper">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="textcontent">
|
||||||
|
<span>
|
||||||
|
${textArg}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
This email is a service by<br/>
|
||||||
|
Task Venture Capital GmbH, Karl-Ferdinand-Braun-Str. 5, 28359 Bremen, Germany<br/>
|
||||||
|
<a style="color: #666666" href="https://legal.task.vc">Legal Info (https://legal.task.vc)</a>
|
||||||
|
<img class="logoBottom" src="https://assetbroker.lossless.one/brandfiles/00general/brightdark_taskvc@2x_transparent.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
public sendRegistrationEmail(signupSessionArg: RegistrationSession, validationTokenArg: string) {
|
||||||
|
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
|
||||||
|
from: 'workspace.global <noreply@mail.workspace.global>',
|
||||||
|
title: 'Verify your Email Address!',
|
||||||
|
to: signupSessionArg.emailAddress,
|
||||||
|
body: this.createBodyString(`
|
||||||
|
<h1>Email Verification for <br><a style="color: #555555;" href="mailto:${
|
||||||
|
signupSessionArg.emailAddress
|
||||||
|
}">${signupSessionArg.emailAddress}</a></h1>
|
||||||
|
<p>It looks like you requested to register an account with us. We just want to make sure it really was you.</p>
|
||||||
|
<p>In case it was you, <b>please continue with the registration process by clicking the button below</b>. Otherwise, please ignore this email.</p>
|
||||||
|
<a href="https://registration.workspace.global/finishregistration?validationtoken=${encodeURI(
|
||||||
|
validationTokenArg
|
||||||
|
)}"><div class="button">
|
||||||
|
continue with registration
|
||||||
|
</div></a>
|
||||||
|
<p>
|
||||||
|
<b>What do I need a workspace.global Account for?</b><br/>
|
||||||
|
The workspace.global Account is needed to log into e.g. <b>social.io</b>
|
||||||
|
</p>
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendAlreadyRegisteredEmail(userArg: User) {
|
||||||
|
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
|
||||||
|
from: 'workspace.global <noreply@mail.workspace.global>',
|
||||||
|
title: 'Login Instead?!',
|
||||||
|
to: userArg.data.email,
|
||||||
|
body: this.createBodyString(`
|
||||||
|
<h1>Email is already registered:<br><a style="color: #555555;" href="mailto:${
|
||||||
|
userArg.data.email
|
||||||
|
}">${userArg.data.email}</a></h1>
|
||||||
|
<p>Someone retried to reregister with the email ${userArg.data.email}</p>
|
||||||
|
<p>In case it was you, <b>please simply log in with your existing account</b>. Otherwise, please ignore this email.</p>
|
||||||
|
<a href="https://account.lossless.org/finishsignup?email=${encodeURI(
|
||||||
|
userArg.data.email
|
||||||
|
)}"><div class="button">
|
||||||
|
Simply login :)
|
||||||
|
</div></a>
|
||||||
|
<p>
|
||||||
|
<b>Forgot your password?</b><br/>
|
||||||
|
Just click the password reset link when logging in.
|
||||||
|
</p>
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendWelcomeEMail(userArg: User) {
|
||||||
|
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
|
||||||
|
from: 'workspace.global <noreply@mail.workspace.global>',
|
||||||
|
title: 'Welcome and Thank You!',
|
||||||
|
to: userArg.data.email,
|
||||||
|
body: this.createBodyString(`
|
||||||
|
<h1>Welcome And Thank You, ${userArg.data.name}</h1>
|
||||||
|
<p>You now have a fully registered workspace.global Account</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>What can I use it for?</b><br/>
|
||||||
|
The workspace.global Account can be used to log into all our apps.<br>
|
||||||
|
Some of them are<br/>
|
||||||
|
${(() => {
|
||||||
|
const products = ['social.io', 'layer.io'];
|
||||||
|
return products.map((productArg) => `<span>${productArg}</span>`).join(' ');
|
||||||
|
})()}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href="https://account.lossless.org/manage/
|
||||||
|
userArg.username
|
||||||
|
)}"><div class="button">
|
||||||
|
Go to my account
|
||||||
|
</div></a>
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendLoginWithEMailMail(userArg: User, validationTokenArg: string) {
|
||||||
|
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
|
||||||
|
from: 'workspace.global <noreply@mail.workspace.global>',
|
||||||
|
title: 'Click to login!',
|
||||||
|
to: userArg.data.email,
|
||||||
|
body: this.createBodyString(`
|
||||||
|
<h1>EMail Login Link for <br><a style="color: #555555;" href="mailto:${
|
||||||
|
userArg.data.email
|
||||||
|
}">${userArg.data.email}</a></h1>
|
||||||
|
<p>It looks like you requested to login passwordless via this email.</p>
|
||||||
|
<p>In case it was you, <b>please continue by clicking the button below</b>. Otherwise, please ignore this email.</p>
|
||||||
|
<a href="https://account.lossless.org/?email=${encodeURI(
|
||||||
|
userArg.data.email
|
||||||
|
)}&validationtoken=${encodeURI(validationTokenArg)}"><div class="button">
|
||||||
|
Login!
|
||||||
|
</div></a>
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public sendPasswordResetMail(userArg: User, validationTokenArg: string) {
|
||||||
|
this.receptionRef.szPlatformClient.emailConnector.sendEmail({
|
||||||
|
from: 'workspace.global <noreply@mail.workspace.global>',
|
||||||
|
title: 'Password reset?',
|
||||||
|
to: userArg.data.email,
|
||||||
|
body: this.createBodyString(`
|
||||||
|
<h1>Password Reset for <br><a style="color: #555555;" href="mailto:${userArg.data.email}">${
|
||||||
|
userArg.data.email
|
||||||
|
}</a></h1>
|
||||||
|
<p>It looks like you requested to reset your password with us.</p>
|
||||||
|
<p>In case it was you, <b>please continue by clicking the button below</b>. Otherwise, please ignore this email.</p>
|
||||||
|
<a href="https://account.lossless.org/?email=${encodeURI(
|
||||||
|
userArg.data.email
|
||||||
|
)}&validationtoken=${encodeURI(validationTokenArg)}"><div class="button">
|
||||||
|
Reset Password
|
||||||
|
</div></a>
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
|
||||||
|
export class ReceptionServer {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public serviceServer: plugins.loleServiceServer.ServiceServer;
|
||||||
|
public typedsocket: plugins.typedsocket.TypedSocket;
|
||||||
|
|
||||||
|
constructor(receptionRef: Reception) {
|
||||||
|
this.receptionRef = receptionRef;
|
||||||
|
this.serviceServer = new plugins.loleServiceServer.ServiceServer({
|
||||||
|
serviceDomain: 'reception.lossless.one',
|
||||||
|
serviceName: 'reception',
|
||||||
|
serviceVersion: this.receptionRef.projectinfoNpm.version,
|
||||||
|
port: parseInt(this.receptionRef.serviceQenv.getEnvVarOnDemand('TEST_PORT')) || 3000,
|
||||||
|
addCustomRoutes: async (serverArg) => {
|
||||||
|
serverArg.addRoute(
|
||||||
|
'/typedrequest',
|
||||||
|
new plugins.loleServiceServer.HandlerTypedRouter(this.receptionRef.typedrouter)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await this.serviceServer.start();
|
||||||
|
this.typedsocket = this.serviceServer.typedServer.typedsocket;
|
||||||
|
this.serviceServer.typedServer.typedrouter.addTypedRouter(this.receptionRef.typedrouter);
|
||||||
|
}
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
await this.typedsocket.stop();
|
||||||
|
await this.serviceServer.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can be used to store binary data for users and organizations
|
||||||
|
*/
|
||||||
|
@plugins.smartdata.Collection(() => {
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
export class ReceptionStorage {}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
import { RegistrationSessionManager } from './classes.registrationsessionmanager.js';
|
||||||
|
import { logger } from './logging.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a RegistrationSession is a in memory session for signing up
|
||||||
|
*/
|
||||||
|
export class RegistrationSession {
|
||||||
|
// ======
|
||||||
|
// STATIC
|
||||||
|
// ======
|
||||||
|
public static async createRegistrationSessionForEmail(
|
||||||
|
registrationSessionManageremailArg: RegistrationSessionManager,
|
||||||
|
emailArg: string
|
||||||
|
) {
|
||||||
|
const newRegistrationSession = new RegistrationSession(
|
||||||
|
registrationSessionManageremailArg,
|
||||||
|
emailArg
|
||||||
|
);
|
||||||
|
const emailValidationResult = await newRegistrationSession
|
||||||
|
.validateEMailAddress()
|
||||||
|
.catch((error) => {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'Error occured during email provider & dns validation'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (!emailValidationResult?.valid) {
|
||||||
|
newRegistrationSession.destroy();
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'Email Address is not valid. Please use a correctly formated email address'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (emailValidationResult.disposable) {
|
||||||
|
newRegistrationSession.destroy();
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'Email is disposable. Please use a non disposable email address.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`${newRegistrationSession.emailAddress} is valid. Continuing registration process!`
|
||||||
|
);
|
||||||
|
await newRegistrationSession.sendTokenValidationEmail();
|
||||||
|
console.log(`Successfully sent email validation email`);
|
||||||
|
return newRegistrationSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========
|
||||||
|
// INSTANCE
|
||||||
|
// ========
|
||||||
|
public registrationSessionManagerRef: RegistrationSessionManager;
|
||||||
|
|
||||||
|
public emailAddress: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* only used during testing
|
||||||
|
*/
|
||||||
|
public unhashedEmailToken?: string;
|
||||||
|
public hashedEmailToken: string;
|
||||||
|
private smsvalidationCounter = 0;
|
||||||
|
public smsCode: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the status of the registration. should progress in a linear fashion.
|
||||||
|
*/
|
||||||
|
public status: 'announced' | 'emailValidated' | 'mobileVerified' | 'registered' | 'failed' =
|
||||||
|
'announced';
|
||||||
|
|
||||||
|
public collectedData: {
|
||||||
|
userData: plugins.lointReception.data.IUser['data'];
|
||||||
|
} = {
|
||||||
|
userData: {
|
||||||
|
username: null,
|
||||||
|
connectedOrgs: [],
|
||||||
|
email: null,
|
||||||
|
name: null,
|
||||||
|
status: null,
|
||||||
|
mobileNumber: null,
|
||||||
|
password: null,
|
||||||
|
passwordHash: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
registrationSessionManagerRefArg: RegistrationSessionManager,
|
||||||
|
emailAddressArg: string
|
||||||
|
) {
|
||||||
|
this.registrationSessionManagerRef = registrationSessionManagerRefArg;
|
||||||
|
this.emailAddress = emailAddressArg;
|
||||||
|
this.registrationSessionManagerRef.registrationSessions.addToMap(this.emailAddress, this);
|
||||||
|
|
||||||
|
// lets destroy this after 10 minutes,
|
||||||
|
// works in unrefed mode so not blocking node exiting.
|
||||||
|
plugins.smartdelay.delayFor(600000, null, true).then(() => this.destroy());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validates a token by comparing its hash against the stored hashed token
|
||||||
|
* @param tokenArg
|
||||||
|
*/
|
||||||
|
public validateEmailToken(tokenArg: string): boolean {
|
||||||
|
const result = this.hashedEmailToken === plugins.smarthash.sha256FromStringSync(tokenArg);
|
||||||
|
if (result && this.status === 'announced') {
|
||||||
|
this.status = 'emailValidated';
|
||||||
|
this.collectedData.userData.email = this.emailAddress;
|
||||||
|
}
|
||||||
|
if (!result && this.status === 'announced') {
|
||||||
|
this.status = 'failed';
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** validates the sms code */
|
||||||
|
public validateSmsCode(smsCodeArg: string) {
|
||||||
|
this.smsvalidationCounter++;
|
||||||
|
const result = this.smsCode === smsCodeArg;
|
||||||
|
if (this.status === 'emailValidated' && result) {
|
||||||
|
this.status = 'mobileVerified';
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
if (this.smsvalidationCounter === 5) {
|
||||||
|
this.destroy();
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'Registration cancelled due to repeated wrong verification code submission'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validate the email address with provider and dns sanity checks
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async validateEMailAddress(): Promise<plugins.smartmail.IEmailValidationResult> {
|
||||||
|
console.log(`validating email ${this.emailAddress}`);
|
||||||
|
const result = await new plugins.smartmail.EmailAddressValidator().validate(this.emailAddress);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* send the validation email
|
||||||
|
*/
|
||||||
|
public async sendTokenValidationEmail() {
|
||||||
|
const uuidToSend = plugins.smartunique.uuid4();
|
||||||
|
this.unhashedEmailToken = uuidToSend;
|
||||||
|
this.hashedEmailToken = plugins.smarthash.sha256FromStringSync(uuidToSend);
|
||||||
|
this.registrationSessionManagerRef.receptionRef.receptionMailer.sendRegistrationEmail(
|
||||||
|
this,
|
||||||
|
uuidToSend
|
||||||
|
);
|
||||||
|
logger.log('info', `sent a validation email with a verification code to ${this.emailAddress}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* validate the mobile number of someone
|
||||||
|
*/
|
||||||
|
public async sendValidationSms() {
|
||||||
|
if (!process.env.TEST_MODE) {
|
||||||
|
this.smsCode =
|
||||||
|
await this.registrationSessionManagerRef.receptionRef.loleSmsClientInstance.sendSmsVerifcation(
|
||||||
|
{
|
||||||
|
fromName: 'w...global',
|
||||||
|
toNumber: parseInt(this.collectedData.userData.mobileNumber),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log('Not sending SMS in automated test mode');
|
||||||
|
this.smsCode = '123456';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this method can be called when this registrationsession is validated
|
||||||
|
* and all data has been set
|
||||||
|
*/
|
||||||
|
public async manifestUserWithAccountData(): Promise<User> {
|
||||||
|
if (this.status !== 'mobileVerified') {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'You can only manifest user that have a validated email Address and Mobile Number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!this.collectedData) {
|
||||||
|
throw new Error('You have to set the accountdata first');
|
||||||
|
}
|
||||||
|
const manifestedUser =
|
||||||
|
await this.registrationSessionManagerRef.receptionRef.userManager.CUser.createNewUserForUserData(
|
||||||
|
this.collectedData.userData
|
||||||
|
);
|
||||||
|
return manifestedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroys the registrationsession
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.registrationSessionManagerRef.registrationSessions.removeFromMap(this.emailAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { RegistrationSession } from './classes.registrationsession.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { logger } from './logging.js';
|
||||||
|
|
||||||
|
export class RegistrationSessionManager {
|
||||||
|
public receptionRef: Reception;
|
||||||
|
|
||||||
|
public registrationSessions = new plugins.lik.FastMap<RegistrationSession>();
|
||||||
|
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
this.receptionRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_FirstRegistration>(
|
||||||
|
'firstRegistrationRequest',
|
||||||
|
async (requestData) => {
|
||||||
|
// check for exiting User
|
||||||
|
const existingUser = await this.receptionRef.userManager.CUser.getInstance({
|
||||||
|
data: {
|
||||||
|
email: requestData.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (existingUser) {
|
||||||
|
this.receptionRef.receptionMailer.sendAlreadyRegisteredEmail(existingUser);
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
`We sent you an Email with more information.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// check for exiting SignupSession
|
||||||
|
const existingSession = this.registrationSessions.getByKey(requestData.email);
|
||||||
|
if (existingSession) {
|
||||||
|
logger.log('warn', `destroyed old signupSession for ${requestData.email}`);
|
||||||
|
existingSession.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// lets check the email before we create a signup session
|
||||||
|
|
||||||
|
const newSignupSession = await RegistrationSession.createRegistrationSessionForEmail(
|
||||||
|
this,
|
||||||
|
requestData.email
|
||||||
|
).catch((e: plugins.typedrequest.TypedResponseError) => {
|
||||||
|
console.log(e.errorText);
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newSignupSession) {
|
||||||
|
logger.log('info', `created signupSession for ${requestData.email}`);
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
testOnlyToken: process.env.TEST_MODE ? newSignupSession.unhashedEmailToken : null,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return { status: 'not ok' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_AfterRegistrationEmailClicked>(
|
||||||
|
'afterRegistrationEmailClicked',
|
||||||
|
async (requestData) => {
|
||||||
|
const signupSession = await this.registrationSessions.find(async (itemArg) =>
|
||||||
|
itemArg.validateEmailToken(requestData.token)
|
||||||
|
);
|
||||||
|
if (signupSession) {
|
||||||
|
return {
|
||||||
|
email: signupSession.emailAddress,
|
||||||
|
status: 'ok',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
email: null,
|
||||||
|
status: 'not ok',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_SetDataForRegistration>(
|
||||||
|
'setDataForRegistration',
|
||||||
|
async (requestData) => {
|
||||||
|
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
|
||||||
|
itemArg.validateEmailToken(requestData.token)
|
||||||
|
);
|
||||||
|
if (!registrationSession) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'could not find a matching signupsession'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestData.userData.name) {
|
||||||
|
registrationSession.collectedData.userData.name = requestData.userData.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestData.userData.password) {
|
||||||
|
registrationSession.collectedData.userData.password = requestData.userData.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_MobileVerificationForRegistration>(
|
||||||
|
'mobileVerificationForRegistration',
|
||||||
|
async (requestData) => {
|
||||||
|
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
|
||||||
|
itemArg.validateEmailToken(requestData.token)
|
||||||
|
);
|
||||||
|
if (!registrationSession) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'could not find a matching signupsession'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check prerequisites
|
||||||
|
if (registrationSession.status === 'announced') {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'You must validate the email address first'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestData.mobileNumber) {
|
||||||
|
registrationSession.status = 'emailValidated';
|
||||||
|
registrationSession.collectedData.userData.mobileNumber = requestData.mobileNumber;
|
||||||
|
await registrationSession.sendValidationSms();
|
||||||
|
return {
|
||||||
|
messageSent: true,
|
||||||
|
testOnlySmsCode: process.env.TEST_MODE ? registrationSession.smsCode : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestData.verificationCode) {
|
||||||
|
const validationResult = registrationSession.validateSmsCode(
|
||||||
|
requestData.verificationCode
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
verficationCodeOk: validationResult,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'you misused the purpose of this TypedHandler'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.typedRouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.lointReception.request.IReq_FinishRegistration>(
|
||||||
|
'finishRegistration',
|
||||||
|
async (requestData) => {
|
||||||
|
const registrationSession = await this.registrationSessions.find(async (itemArg) =>
|
||||||
|
itemArg.validateEmailToken(requestData.token)
|
||||||
|
);
|
||||||
|
if (!registrationSession) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError(
|
||||||
|
'could not find a matching signupsession'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultingUser = await registrationSession.manifestUserWithAccountData();
|
||||||
|
registrationSession.destroy();
|
||||||
|
this.receptionRef.receptionMailer.sendWelcomeEMail(resultingUser);
|
||||||
|
return {
|
||||||
|
accountData: {
|
||||||
|
id: resultingUser.id,
|
||||||
|
data: {
|
||||||
|
email: resultingUser.data.email,
|
||||||
|
name: resultingUser.data.name,
|
||||||
|
username: resultingUser.data.username,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: 'ok',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
@plugins.smartdata.Manager()
|
||||||
|
export class Role extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
Role,
|
||||||
|
plugins.lointReception.data.IRole
|
||||||
|
> {
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
data: plugins.lointReception.data.IRole['data'];
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Organization } from './classes.organization.js';
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { Role } from './classes.role.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class RoleManager {
|
||||||
|
// INSTANCE
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
public CRole = plugins.smartdata.setDefaultManagerForDoc(this, Role);
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async modifyRoleForUserAtOrg(optionsArg: {
|
||||||
|
action: 'create' | 'change' | 'delete';
|
||||||
|
userId: string;
|
||||||
|
organizationId: string;
|
||||||
|
role: plugins.lointReception.data.IRole['data']['role'];
|
||||||
|
}) {
|
||||||
|
let returnRole: Role;
|
||||||
|
switch (optionsArg.action) {
|
||||||
|
case 'create':
|
||||||
|
returnRole = new this.CRole();
|
||||||
|
returnRole.id = plugins.smartunique.shortId();
|
||||||
|
returnRole.data = {
|
||||||
|
userId: optionsArg.userId,
|
||||||
|
organizationId: optionsArg.organizationId,
|
||||||
|
role: optionsArg.role,
|
||||||
|
};
|
||||||
|
await returnRole.save();
|
||||||
|
}
|
||||||
|
return returnRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRoleForUserAndOrg(userArg: User, orgArg: Organization) {
|
||||||
|
const role = await this.CRole.getInstance({
|
||||||
|
data: {
|
||||||
|
userId: userArg.id,
|
||||||
|
organizationId: orgArg.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAllRolesForUser(userArg: User) {
|
||||||
|
const roles = await this.CRole.getInstances({
|
||||||
|
data: {
|
||||||
|
userId: userArg.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { UserManager } from './classes.usermanager.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a User is identified by its username or email.
|
||||||
|
* Both need to be unique and both can be changed.
|
||||||
|
*/
|
||||||
|
@plugins.smartdata.Manager()
|
||||||
|
export class User extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
User,
|
||||||
|
plugins.lointReception.data.IUser
|
||||||
|
> {
|
||||||
|
// STATIC
|
||||||
|
public static async createNewUserForUserData(
|
||||||
|
userDataArg: plugins.lointReception.data.IUser['data']
|
||||||
|
): Promise<User> {
|
||||||
|
const newUser = new User();
|
||||||
|
newUser.id = plugins.smartunique.shortId();
|
||||||
|
newUser.data = {
|
||||||
|
connectedOrgs: null,
|
||||||
|
status: 'new',
|
||||||
|
name: userDataArg.name,
|
||||||
|
username: userDataArg.username,
|
||||||
|
email: userDataArg.email,
|
||||||
|
passwordHash: userDataArg.passwordHash,
|
||||||
|
};
|
||||||
|
if (!newUser.data.passwordHash && userDataArg.password) {
|
||||||
|
newUser.data.passwordHash = await User.hashPassword(userDataArg.password);
|
||||||
|
}
|
||||||
|
await newUser.save();
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hashPassword(passwordArg: string) {
|
||||||
|
return plugins.smarthash.sha256FromString(passwordArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public data: plugins.lointReception.data.IUser['data'];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLegalData() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a user manager
|
||||||
|
*/
|
||||||
|
export class UserManager {
|
||||||
|
// refs
|
||||||
|
public receptionRef: Reception;
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
public get db() {
|
||||||
|
return this.receptionRef.db.smartdataDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// classes
|
||||||
|
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
|
||||||
|
|
||||||
|
constructor(receptionRefArg: Reception) {
|
||||||
|
this.receptionRef = receptionRefArg;
|
||||||
|
this.receptionRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
this.typedrouter.addTypedHandler<plugins.lointReception.request.IReq_GetRolesAndOrganizationsForUserId>(
|
||||||
|
new plugins.typedrequest.TypedHandler('getRolesAndOrganizationsForUserId', async reqArg => {
|
||||||
|
const user = await this.getUserByJwtValidation(reqArg.jwt);
|
||||||
|
const organizations = await this.receptionRef.organizationmanager.getAllOrganizationsForUser(
|
||||||
|
user
|
||||||
|
);
|
||||||
|
const roles = await this.receptionRef.roleManager.getAllRolesForUser(user);
|
||||||
|
return {
|
||||||
|
organizations,
|
||||||
|
roles
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the user by validating a JWT
|
||||||
|
*/
|
||||||
|
public async getUserByJwt(jwtString: string) {
|
||||||
|
const jwtInstance = await this.receptionRef.jwtManager.verifyJWTAndGetData(jwtString);
|
||||||
|
const user = await this.CUser.getInstance({
|
||||||
|
id: jwtInstance.data.userId
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* just validate jwt
|
||||||
|
* faster than the "getUserByJwt"
|
||||||
|
*/
|
||||||
|
public async getUserByJwtValidation(jwtStringArg: string) {
|
||||||
|
const jwtDataArg: plugins.lointReception.data.IJwt = await this.receptionRef.jwtManager.smartjwtInstance.verifyJWTAndGetData(jwtStringArg);
|
||||||
|
const resultingUser = await this.CUser.getInstance({
|
||||||
|
id: jwtDataArg.data.userId
|
||||||
|
});
|
||||||
|
return resultingUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// general exports for testing
|
||||||
|
export * from './classes.reception.js';
|
||||||
|
|
||||||
|
// running it in production
|
||||||
|
import { Reception } from './classes.reception.js';
|
||||||
|
|
||||||
|
let reception: Reception;
|
||||||
|
export const runCli = async () => {
|
||||||
|
reception = new Reception();
|
||||||
|
await reception.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stop = async () => {
|
||||||
|
await reception.stop();
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
|
const projectinfoNpm = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
|
||||||
|
|
||||||
|
export const logger = plugins.loleLog.createLoleLogger({
|
||||||
|
companyUnit: 'Lossless Cloud',
|
||||||
|
containerName: 'reception',
|
||||||
|
containerVersion: projectinfoNpm.version,
|
||||||
|
sentryAppName: 'reception',
|
||||||
|
sentryDsn: 'https://fd929bdcad0a41c0b7853cdea04f9c96@o169278.ingest.sentry.io/5272722',
|
||||||
|
zone: 'servezone',
|
||||||
|
});
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// node native
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export { path };
|
||||||
|
|
||||||
|
// project scope
|
||||||
|
import * as lointReception from '../../dist_ts_interfaces/index.js';
|
||||||
|
|
||||||
|
export { lointReception, };
|
||||||
|
|
||||||
|
// @apiglobal scope
|
||||||
|
import * as typedrequest from '@api.global/typedrequest';
|
||||||
|
import * as typedsocket from '@api.global/typedsocket';
|
||||||
|
|
||||||
|
export { typedrequest, typedsocket };
|
||||||
|
|
||||||
|
// @serve.zone scope
|
||||||
|
import * as szPlatformClient from '@serve.zone/platformclient';
|
||||||
|
|
||||||
|
export { szPlatformClient };
|
||||||
|
|
||||||
|
|
||||||
|
// @pushrocks scope
|
||||||
|
import * as lik from '@push.rocks/lik';
|
||||||
|
import * as projectinfo from '@push.rocks/projectinfo';
|
||||||
|
import * as qenv from '@push.rocks/qenv';
|
||||||
|
import * as smartdata from '@push.rocks/smartdata';
|
||||||
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
|
import * as smartmail from '@push.rocks/smartmail';
|
||||||
|
import * as smarthash from '@push.rocks/smarthash';
|
||||||
|
import * as smartjwt from '@push.rocks/smartjwt';
|
||||||
|
import * as smartpath from '@push.rocks/smartpath';
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
import * as smarttime from '@push.rocks/smarttime';
|
||||||
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
|
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||||
|
|
||||||
|
export {
|
||||||
|
lik,
|
||||||
|
projectinfo,
|
||||||
|
qenv,
|
||||||
|
smartdata,
|
||||||
|
smartdelay,
|
||||||
|
smartmail,
|
||||||
|
smarthash,
|
||||||
|
smartjwt,
|
||||||
|
smartpath,
|
||||||
|
smartpromise,
|
||||||
|
smarttime,
|
||||||
|
smartunique,
|
||||||
|
taskbuffer,
|
||||||
|
};
|
||||||
|
|
||||||
|
// @tsclass scope
|
||||||
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
|
export { tsclass };
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
import { IdpRequests } from './classes.idprequests.js';
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class IdpClient {
|
||||||
|
// INSTANCE PRIVATE
|
||||||
|
private helpers = {
|
||||||
|
async extractDataFromJwtString(jwtString: string): Promise<plugins.lointReception.data.IJwt> {
|
||||||
|
return plugins.webjwt.getDataFromJwtString(jwtString);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// INSTANCE PUBLIC
|
||||||
|
|
||||||
|
public appData: plugins.lointReception.data.IApp;
|
||||||
|
public rolesReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1);
|
||||||
|
public organizationsReplaySubject = new plugins.smartrx.rxjs.ReplaySubject(1);
|
||||||
|
|
||||||
|
public receptionTrUrl: string;
|
||||||
|
constructor(receptionBaseUrlArg: string, appDataArg?: plugins.lointReception.data.IApp) {
|
||||||
|
this.receptionTrUrl = receptionBaseUrlArg
|
||||||
|
if (this.receptionTrUrl.endsWith('/')) {
|
||||||
|
this.receptionTrUrl = this.receptionTrUrl.slice(0, -1);
|
||||||
|
}
|
||||||
|
if (!this.receptionTrUrl.endsWith('/typedrequest')) {
|
||||||
|
this.receptionTrUrl = `${this.receptionTrUrl}/typedrequest`;
|
||||||
|
}
|
||||||
|
console.log(`reception client connecting to ${this.receptionTrUrl}`);
|
||||||
|
if (!appDataArg) {
|
||||||
|
appDataArg = {
|
||||||
|
id: '', // TODO
|
||||||
|
appUrl: `https://${window.location.host}/`,
|
||||||
|
description: null,
|
||||||
|
logoUrl: null,
|
||||||
|
name: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.appData = appDataArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public requests = new IdpRequests(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* app data can be transferred when redirecting to the sso domain using query params
|
||||||
|
* this message retrieves the app data when on the sso domain
|
||||||
|
*/
|
||||||
|
public async getAppDataOnSsoDomain() {
|
||||||
|
if (!window.location.href.startsWith('https://sso.workspace.global/')) {
|
||||||
|
console.error('You are trying to access SSO appData on a non sso domain.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const appDataString = plugins.smarturl.Smarturl.createFromUrl(window.location.href).searchParams
|
||||||
|
.appdata;
|
||||||
|
if (!appDataString) {
|
||||||
|
console.error('no appdata query arg detected');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const appData = plugins.smartjson.parseBase64(appDataString);
|
||||||
|
return appData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setJwt(jwtStringArg: string) {
|
||||||
|
await this.storeJwt(jwtStringArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a typedsocket for going reactive
|
||||||
|
*/
|
||||||
|
public typedsocket: plugins.typedsocket.TypedSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a typed router to go reactive
|
||||||
|
*/
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
|
||||||
|
public statusObservable =
|
||||||
|
new plugins.smartrx.rxjs.Subject<plugins.lointReception.data.TLoginStatus>();
|
||||||
|
|
||||||
|
public ssoStore = new plugins.webstore.WebStore({
|
||||||
|
storeName: 'wgsso',
|
||||||
|
dbName: 'wgsso',
|
||||||
|
});
|
||||||
|
|
||||||
|
public async storeJwt(jwtString: string) {
|
||||||
|
await this.ssoStore.set('wgJwt', jwtString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getJwt(): Promise<string> {
|
||||||
|
return await this.ssoStore.get('wgJwt');
|
||||||
|
}
|
||||||
|
public async getJwtData(): Promise<plugins.lointReception.data.IJwt> {
|
||||||
|
return this.helpers.extractDataFromJwtString(await this.getJwt());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteJwt() {
|
||||||
|
await this.ssoStore.delete('wgJwt');
|
||||||
|
console.log('removed jwt');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* performs jwt housekeeping
|
||||||
|
* only call if jwt is present
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async performJwtHousekeeping() {
|
||||||
|
let jwt = await this.getJwt();
|
||||||
|
if (!jwt) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const extractedJwt = await this.helpers.extractDataFromJwtString(jwt);
|
||||||
|
if (extractedJwt.data.refreshFrom < Date.now() && Date.now() < extractedJwt.data.validUntil) {
|
||||||
|
jwt = await this.refreshJwt();
|
||||||
|
} else if (Date.now() > extractedJwt.data.validUntil) {
|
||||||
|
this.deleteJwt();
|
||||||
|
}
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async refreshJwt(refreshTokenArg?: string): Promise<string> {
|
||||||
|
let extractedJwt: plugins.lointReception.data.IJwt;
|
||||||
|
|
||||||
|
if (!refreshTokenArg) {
|
||||||
|
extractedJwt = await this.helpers.extractDataFromJwtString(await this.getJwt());
|
||||||
|
}
|
||||||
|
const refreshJwtReq =
|
||||||
|
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_RefreshJwt>(
|
||||||
|
`${this.receptionTrUrl}/typedrequest`,
|
||||||
|
'refreshJwt'
|
||||||
|
);
|
||||||
|
const response = await refreshJwtReq.fire({
|
||||||
|
refreshToken: refreshTokenArg || extractedJwt.data.refreshToken,
|
||||||
|
});
|
||||||
|
if (response.jwt) {
|
||||||
|
await this.storeJwt(response.jwt);
|
||||||
|
} else {
|
||||||
|
await this.deleteJwt();
|
||||||
|
}
|
||||||
|
this.statusObservable.next(response.status);
|
||||||
|
return await this.getJwt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* can be used to switch between pages
|
||||||
|
*/
|
||||||
|
public async getTransferToken(appDataArg?: plugins.lointReception.data.IApp): Promise<string> {
|
||||||
|
const jwt = await this.performJwtHousekeeping();
|
||||||
|
const extractedJwt = await this.helpers.extractDataFromJwtString(jwt);
|
||||||
|
const getTransferToken =
|
||||||
|
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
|
||||||
|
`${this.receptionTrUrl}/typedrequest`,
|
||||||
|
'exchangeRefreshTokenAndTransferToken'
|
||||||
|
);
|
||||||
|
const response = await getTransferToken.fire({
|
||||||
|
refreshToken: extractedJwt.data.refreshToken,
|
||||||
|
appData: appDataArg || this.appData,
|
||||||
|
});
|
||||||
|
return response.transferToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets a transfer token and switches to a location
|
||||||
|
*/
|
||||||
|
public async getTransferTokenAndSwitchToLocation(newLocationArg: string): Promise<void> {
|
||||||
|
const transferToken = await this.getTransferToken();
|
||||||
|
if (!transferToken) {
|
||||||
|
alert('failed to get transfer token!');
|
||||||
|
}
|
||||||
|
const urlInstance = plugins.smarturl.Smarturl.createFromUrl(newLocationArg, {
|
||||||
|
searchParams: {
|
||||||
|
transfertoken: transferToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const transferUrl = urlInstance.toString();
|
||||||
|
window.location.href = transferUrl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* processes a transfer token
|
||||||
|
*/
|
||||||
|
public async processTransferToken(): Promise<boolean> {
|
||||||
|
const href = window.location.href;
|
||||||
|
const url = plugins.smarturl.Smarturl.createFromUrl(href);
|
||||||
|
const transferToken = url.searchParams['transfertoken'];
|
||||||
|
if (transferToken) {
|
||||||
|
const getTransferToken =
|
||||||
|
new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
|
||||||
|
`${this.receptionTrUrl}/typedrequest`,
|
||||||
|
'exchangeRefreshTokenAndTransferToken'
|
||||||
|
);
|
||||||
|
const response = await getTransferToken.fire({
|
||||||
|
transferToken,
|
||||||
|
appData: this.appData,
|
||||||
|
});
|
||||||
|
if (response.refreshToken) {
|
||||||
|
await this.refreshJwt(response.refreshToken);
|
||||||
|
} else {
|
||||||
|
globalThis.alert?.('transfer token invalid');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login Status stuff
|
||||||
|
public async checkJwtPresent() {
|
||||||
|
const jwt = await this.performJwtHousekeeping();
|
||||||
|
if (jwt) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* forces the current user to login
|
||||||
|
* @param requireLoginArg
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async determineLoginStatus(requireLoginArg: boolean = false): Promise<boolean> {
|
||||||
|
const jwtPresent = await this.checkJwtPresent();
|
||||||
|
if (jwtPresent) {
|
||||||
|
const jwt = await this.performJwtHousekeeping();
|
||||||
|
return !!jwt;
|
||||||
|
} else {
|
||||||
|
const transferTokenResult = await this.processTransferToken();
|
||||||
|
if (transferTokenResult) {
|
||||||
|
// we are in the clear
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (requireLoginArg) {
|
||||||
|
const urlInstance = plugins.smarturl.Smarturl.createFromUrl(
|
||||||
|
'https://sso.workspace.global/',
|
||||||
|
{
|
||||||
|
searchParams: {
|
||||||
|
appdata: plugins.smartjson.stringifyBase64(this.appData),
|
||||||
|
action: 'login',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (!globalThis.location.href.startsWith('https://sso.workspace.global/')) {
|
||||||
|
globalThis.location.href = urlInstance.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logs out the current user
|
||||||
|
*/
|
||||||
|
public async logout() {
|
||||||
|
const urlInstance = plugins.smarturl.Smarturl.createFromUrl('https://sso.workspace.global/', {
|
||||||
|
searchParams: {
|
||||||
|
appdata: plugins.smartjson.stringifyBase64(this.appData),
|
||||||
|
action: 'logout',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!globalThis.location.href.startsWith('https://sso.workspace.global/')) {
|
||||||
|
// we are somewhere in an app
|
||||||
|
await this.deleteJwt();
|
||||||
|
globalThis.location.href = urlInstance.toString();
|
||||||
|
} else {
|
||||||
|
// we are in the sso page
|
||||||
|
await this.enableTypedSocket();
|
||||||
|
console.log(`logging out against ${this.receptionTrUrl}`)
|
||||||
|
const logoutTr =
|
||||||
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.ILogoutRequest>(
|
||||||
|
'logout'
|
||||||
|
);
|
||||||
|
await logoutTr.fire({
|
||||||
|
refreshToken: (await this.getJwtData()).data.refreshToken,
|
||||||
|
});
|
||||||
|
await this.deleteJwt();
|
||||||
|
const appData = await this.getAppDataOnSsoDomain();
|
||||||
|
if (appData) {
|
||||||
|
console.log(`redirecting to app after logout: ${appData.appUrl}`);
|
||||||
|
window.location.href = appData.appUrl;
|
||||||
|
} else {
|
||||||
|
console.error('no appData provided. Not redirecting after logout.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public typedsocketDeferred = plugins.smartpromise.defer();
|
||||||
|
public async enableTypedSocket() {
|
||||||
|
if (this.typedsocketDeferred.claimed) {
|
||||||
|
return this.typedsocketDeferred.promise;
|
||||||
|
}
|
||||||
|
this.typedsocketDeferred.claim();
|
||||||
|
this.typedsocket = await plugins.typedsocket.TypedSocket.createClient(
|
||||||
|
this.typedrouter,
|
||||||
|
`${this.receptionTrUrl}/`
|
||||||
|
);
|
||||||
|
this.typedsocketDeferred.resolve(this.typedsocket);
|
||||||
|
return this.typedsocketDeferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop() {
|
||||||
|
await this.typedsocket?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================================
|
||||||
|
// Organization and Settings stuff
|
||||||
|
// ==================================
|
||||||
|
public async createOrganization(
|
||||||
|
orgNameArg: string,
|
||||||
|
orgSlugArg: string,
|
||||||
|
modeArg: 'checkAvailability' | 'manifest'
|
||||||
|
) {
|
||||||
|
await this.typedsocketDeferred.promise;
|
||||||
|
const validateOrg =
|
||||||
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_CreateOrganization>(
|
||||||
|
'createOrganization'
|
||||||
|
);
|
||||||
|
const response = await validateOrg.fire({
|
||||||
|
jwt: await this.getJwt(),
|
||||||
|
action: modeArg,
|
||||||
|
organizationName: orgNameArg,
|
||||||
|
organizationSlug: orgSlugArg,
|
||||||
|
userId: (await this.getJwtData()).id,
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the current OrganizationRoles
|
||||||
|
*/
|
||||||
|
public async getRolesAndOrganizations() {
|
||||||
|
await this.typedsocketDeferred.promise;
|
||||||
|
const rolesAndOrganizationsForUserId =
|
||||||
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_GetRolesAndOrganizationsForUserId>(
|
||||||
|
'getRolesAndOrganizationsForUserId'
|
||||||
|
);
|
||||||
|
const response = await rolesAndOrganizationsForUserId.fire({
|
||||||
|
jwt: await this.getJwt(),
|
||||||
|
userId: (await this.getJwtData()).id,
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updates the PaddleCheckoutId for an organization.
|
||||||
|
*/
|
||||||
|
public async updatePaddleCheckoutId(orgIdArg: string, checkoutIdArg: string) {
|
||||||
|
await this.typedsocketDeferred.promise;
|
||||||
|
const updateBillingPlan =
|
||||||
|
this.typedsocket.createTypedRequest<plugins.lointReception.request.IReq_UpdatePaymentMethod>(
|
||||||
|
'updatePaymentMethod'
|
||||||
|
);
|
||||||
|
const response = await updateBillingPlan.fire({
|
||||||
|
jwtString: await this.getJwt(),
|
||||||
|
orgId: orgIdArg,
|
||||||
|
paddle: {
|
||||||
|
checkoutId: checkoutIdArg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import type { IdpClient } from "./classes.idpclient.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this class bundles all the typed requests that are used by the idp
|
||||||
|
*/
|
||||||
|
export class IdpRequests {
|
||||||
|
idpClientArg: IdpClient;
|
||||||
|
constructor(idpClientArg: IdpClient) {
|
||||||
|
this.idpClientArg = idpClientArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get afterRegistrationEmailClicked () {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_AfterRegistrationEmailClicked>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'afterRegistrationEmailClicked'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get setData() {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_SetDataForRegistration>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'setDataForRegistration'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get mobileNumberVerification () {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_MobileVerificationForRegistration>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'mobileVerificationForRegistration'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public get finishRegistration() {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_FinishRegistration>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'finishRegistration'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get loginWithUserNameAndPassword () {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_LoginWithEmailOrUsernameAndPassword>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'loginWithEmailOrUsernameAndPassword'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get obtainJwt () {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_RefreshJwt>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'refreshJwt'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get obtainOneTimeToken () {
|
||||||
|
return new plugins.typedrequest.TypedRequest<plugins.lointReception.request.IReq_ExchangeRefreshTokenAndTransferToken>(
|
||||||
|
this.idpClientArg.receptionTrUrl,
|
||||||
|
'exchangeRefreshTokenAndTransferToken'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './classes.idpclient.js';
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// losslessone_private scope
|
||||||
|
import * as lointReception from '../dist_ts_interfaces/index.js';
|
||||||
|
|
||||||
|
export { lointReception };
|
||||||
|
|
||||||
|
// apiglobal scope
|
||||||
|
import * as typedrequest from '@api.global/typedrequest';
|
||||||
|
import * as typedsocket from '@api.global/typedsocket';
|
||||||
|
|
||||||
|
export { typedrequest, typedsocket };
|
||||||
|
|
||||||
|
// pushrocks scope
|
||||||
|
import * as smartjson from '@push.rocks/smartjson';
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
|
import * as smarttime from '@push.rocks/smarttime';
|
||||||
|
import * as smarturl from '@push.rocks/smarturl';
|
||||||
|
import * as webjwt from '@push.rocks/webjwt';
|
||||||
|
import * as webstore from '@push.rocks/webstore';
|
||||||
|
|
||||||
|
export { smartjson, smartpromise, smartrx, smarttime, smarturl, webjwt, webstore };
|
||||||
|
|
||||||
|
// @tsclass scope
|
||||||
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
|
export { tsclass };
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
|
*/
|
||||||
|
export const commitinfo = {
|
||||||
|
name: '@losslessone_private/loint-reception',
|
||||||
|
version: '1.0.122',
|
||||||
|
description: 'an interface package for the reception service at Lossless'
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export * from './loint-reception.app.js';
|
||||||
|
export * from './loint-reception.billingplan.js';
|
||||||
|
export * from './loint-reception.device.js';
|
||||||
|
export * from './loint-reception.jwt.js';
|
||||||
|
export * from './loint-reception.loginsession.js';
|
||||||
|
export * from './loint-reception.organization.js';
|
||||||
|
export * from './loint-reception.paddlecheckoutdata.js';
|
||||||
|
export * from './loint-reception.role.js';
|
||||||
|
export * from './loint-reception.user.js';
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export interface IApp {
|
||||||
|
/**
|
||||||
|
* must be unique
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* should be unique
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
logoUrl: string;
|
||||||
|
appUrl: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export type TSupportedCurrency = 'EUR';
|
||||||
|
|
||||||
|
export interface IBillableItem {
|
||||||
|
name: string;
|
||||||
|
monthlyPrice: number;
|
||||||
|
currency: TSupportedCurrency;
|
||||||
|
from: number;
|
||||||
|
to: number;
|
||||||
|
factoredOn30DayMonth: number;
|
||||||
|
quantity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBillingPlan {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
type: 'Paddle' | 'AppSumo' | 'FairUsageFree' | 'Enterprise' | 'Internal' | 'Testing';
|
||||||
|
proEnabled: boolean;
|
||||||
|
organizationId: string;
|
||||||
|
lastProcessed: number;
|
||||||
|
seats: number;
|
||||||
|
status: 'active' | 'activeOverdue' | 'pausedOverdue' | 'inactive' | 'suspended';
|
||||||
|
paddleData?: {
|
||||||
|
checkoutId: string;
|
||||||
|
};
|
||||||
|
alternativePaymentData?: {
|
||||||
|
enterprise: boolean;
|
||||||
|
appSumoCode: string;
|
||||||
|
};
|
||||||
|
nextBilling: {
|
||||||
|
items: Array<IBillableItem>;
|
||||||
|
method: 'paddle';
|
||||||
|
ontrack: boolean;
|
||||||
|
errorText?: string;
|
||||||
|
selectedBillingDate: number;
|
||||||
|
};
|
||||||
|
billingEvents: Array<{
|
||||||
|
timestamp: number;
|
||||||
|
amount: number;
|
||||||
|
currency: TSupportedCurrency;
|
||||||
|
billedItems: Array<IBillableItem>;
|
||||||
|
checkoutLink?: string;
|
||||||
|
}>;
|
||||||
|
communications: Array<any>;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export interface IDevice extends plugins.tsclass.network.IDevice {}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
export type TLoginStatus = 'loggedIn' | 'loggedOut' | 'invalidated' | 'not found' | 'transfer';
|
||||||
|
export type TLoginAction = 'login' | 'logout' | 'manage';
|
||||||
|
|
||||||
|
export interface IJwt {
|
||||||
|
id: string;
|
||||||
|
blocked: boolean;
|
||||||
|
data: {
|
||||||
|
/**
|
||||||
|
* the user id of the jwt
|
||||||
|
*/
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the latest point of
|
||||||
|
*/
|
||||||
|
validUntil: number;
|
||||||
|
/**
|
||||||
|
* hold off from refreshing before
|
||||||
|
*/
|
||||||
|
refreshFrom: number;
|
||||||
|
/**
|
||||||
|
* an interval in millis to recheck token invalidation
|
||||||
|
*/
|
||||||
|
refreshEvery: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the refresh token to obtain a new jwt for a session
|
||||||
|
*/
|
||||||
|
refreshToken: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* just for looks/debugging
|
||||||
|
*/
|
||||||
|
justForLooks: {
|
||||||
|
validUntilIsoString: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export interface ILoginSession {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
userId: string;
|
||||||
|
validUntil: number;
|
||||||
|
invalidated: boolean;
|
||||||
|
refreshToken: string;
|
||||||
|
/**
|
||||||
|
* a device id that can be used to share the login session
|
||||||
|
* in different contexts on the same device
|
||||||
|
*/
|
||||||
|
deviceId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import { type IBillingPlan } from './loint-reception.billingplan.js';
|
||||||
|
import { type IRole } from './loint-reception.role.js';
|
||||||
|
|
||||||
|
export interface IOrganization {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
billingPlanId: string;
|
||||||
|
roleIds: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
export interface IPaddleCheckoutData<TPassthrough = null> {
|
||||||
|
checkout: {
|
||||||
|
created_at: string;
|
||||||
|
completed: boolean;
|
||||||
|
id: string;
|
||||||
|
coupon: {
|
||||||
|
coupon_code?: string;
|
||||||
|
};
|
||||||
|
passthrough?: TPassthrough;
|
||||||
|
prices: {
|
||||||
|
customer: {
|
||||||
|
currency: string;
|
||||||
|
unit: string;
|
||||||
|
unit_tax: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
items: Array<{
|
||||||
|
checkout_product_id: number;
|
||||||
|
product_id: number;
|
||||||
|
name: string;
|
||||||
|
custom_message: string;
|
||||||
|
quantity: number;
|
||||||
|
allow_quantity: false;
|
||||||
|
icon_url: string;
|
||||||
|
min_quantity: number;
|
||||||
|
max_quantity: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
/**
|
||||||
|
* factorised, not percentage, so looks like 0.19 for Germany.
|
||||||
|
*/
|
||||||
|
tax_rate: number;
|
||||||
|
recurring: {
|
||||||
|
period: string;
|
||||||
|
interval: number;
|
||||||
|
trial_days: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
vendor: {
|
||||||
|
currency: string;
|
||||||
|
unit: string;
|
||||||
|
unit_tax: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
checkout_product_id: number;
|
||||||
|
product_id: number;
|
||||||
|
name: string;
|
||||||
|
custom_message: string;
|
||||||
|
quantity: number;
|
||||||
|
allow_quantity: false;
|
||||||
|
icon_url: string;
|
||||||
|
min_quantity: number;
|
||||||
|
max_quantity: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
recurring: {
|
||||||
|
period: string;
|
||||||
|
interval: number;
|
||||||
|
trial_days: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
redirect_url: null;
|
||||||
|
test_variant: 'newCheckout';
|
||||||
|
recurring_prices: {
|
||||||
|
customer: {
|
||||||
|
currency: string;
|
||||||
|
unit: string;
|
||||||
|
unit_tax: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
checkout_product_id: number;
|
||||||
|
product_id: number;
|
||||||
|
name: string;
|
||||||
|
custom_message: string;
|
||||||
|
quantity: number;
|
||||||
|
allow_quantity: false;
|
||||||
|
icon_url: string;
|
||||||
|
min_quantity: number;
|
||||||
|
max_quantity: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
recurring: {
|
||||||
|
period: string;
|
||||||
|
interval: number;
|
||||||
|
trial_days: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
interval: {
|
||||||
|
length: number;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
vendor: {
|
||||||
|
currency: string;
|
||||||
|
unit: string;
|
||||||
|
unit_tax: string;
|
||||||
|
total: string;
|
||||||
|
total_tax: string;
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
checkout_product_id: number;
|
||||||
|
product_id: number;
|
||||||
|
name: string;
|
||||||
|
custom_message: string;
|
||||||
|
quantity: number;
|
||||||
|
allow_quantity: false;
|
||||||
|
icon_url: string;
|
||||||
|
min_quantity: number;
|
||||||
|
max_quantity: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
recurring: {
|
||||||
|
period: string;
|
||||||
|
interval: number;
|
||||||
|
trial_days: number;
|
||||||
|
currency: string;
|
||||||
|
unit_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
line_price: {
|
||||||
|
net: number;
|
||||||
|
gross: number;
|
||||||
|
net_discount: number;
|
||||||
|
gross_discount: number;
|
||||||
|
net_after_discount: number;
|
||||||
|
gross_after_discount: number;
|
||||||
|
tax: number;
|
||||||
|
tax_after_discount: number;
|
||||||
|
};
|
||||||
|
discounts: [];
|
||||||
|
tax_rate: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
product: {
|
||||||
|
quantity: number;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
country: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import { type IRole } from './loint-reception.role.js';
|
||||||
|
|
||||||
|
export interface ISubOrgProperty {
|
||||||
|
name: string;
|
||||||
|
domain: string;
|
||||||
|
roles: IRole[];
|
||||||
|
/**
|
||||||
|
* contains the ids of all the apps that show the property
|
||||||
|
*/
|
||||||
|
attributedAppIds: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a role describes a
|
||||||
|
*/
|
||||||
|
export interface IRole {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
userId: string;
|
||||||
|
organizationId: string;
|
||||||
|
role: 'owner' | 'admin' | 'editor' | 'guest' | 'viewer' | 'outlaw';
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import { type IRole } from './loint-reception.role.js';
|
||||||
|
|
||||||
|
export interface IUser {
|
||||||
|
id: string;
|
||||||
|
data: {
|
||||||
|
name: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mobile number used for verification
|
||||||
|
*/
|
||||||
|
mobileNumber?: string;
|
||||||
|
/**
|
||||||
|
* only used during initial password setting
|
||||||
|
*/
|
||||||
|
password?: string;
|
||||||
|
/**
|
||||||
|
* used for validation of passwords
|
||||||
|
*/
|
||||||
|
passwordHash?: string;
|
||||||
|
status: 'new' | 'active' | 'deleted' | 'suspended';
|
||||||
|
/**
|
||||||
|
* a quick ref for which organizations might have roles for this user
|
||||||
|
* speeds up lookup
|
||||||
|
*/
|
||||||
|
connectedOrgs: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// requests
|
||||||
|
import * as request from './request/index.js';
|
||||||
|
import * as data from './data/index.js';
|
||||||
|
import * as tags from './tags/index.js';
|
||||||
|
|
||||||
|
export { request, data, tags };
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// @apiglobal scope
|
||||||
|
import * as typedRequestInterfaces from '@api.global/typedrequest-interfaces';
|
||||||
|
|
||||||
|
export { typedRequestInterfaces };
|
||||||
|
|
||||||
|
// @tsclass scope
|
||||||
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
|
export { tsclass };
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export * from './loint-reception.apitoken.js';
|
||||||
|
export * from './loint-reception.authorization.js';
|
||||||
|
export * from './loint-reception.billingplan.js';
|
||||||
|
export * from './loint-reception.jwt.js';
|
||||||
|
export * from './loint-reception.login.js';
|
||||||
|
export * from './loint-reception.organization.js';
|
||||||
|
export * from './loint-reception.plan.js';
|
||||||
|
export * from './loint-reception.registration.js';
|
||||||
|
export * from './loint-reception.user.js';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import { type IUser, type IRole } from '../data/index.js';
|
||||||
|
|
||||||
|
export interface IReq_InternalAuthorization
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_InternalAuthorization
|
||||||
|
> {
|
||||||
|
method: '';
|
||||||
|
request: {
|
||||||
|
accountData: IUser;
|
||||||
|
jwt: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
accountData: IUser;
|
||||||
|
jwt: string;
|
||||||
|
relevantRoles: IRole[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import * as data from '../data/index.js';
|
||||||
|
|
||||||
|
export interface IReq_UpdatePaymentMethod
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_UpdatePaymentMethod
|
||||||
|
> {
|
||||||
|
method: 'updatePaymentMethod';
|
||||||
|
request: {
|
||||||
|
jwtString: string;
|
||||||
|
orgId: string;
|
||||||
|
paddle?: {
|
||||||
|
checkoutId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
billingPlan: plugins.tsclass.typeFest.PartialDeep<data.IBillingPlan>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows getting the billing plan for a user
|
||||||
|
*/
|
||||||
|
export interface IReq_GetBillingPlan
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetBillingPlan
|
||||||
|
> {
|
||||||
|
method: 'getBillingPlan';
|
||||||
|
request: {
|
||||||
|
jwtString: string;
|
||||||
|
orgId: string;
|
||||||
|
billingPlanId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
billingPlan: data.IBillingPlan;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import * as data from '../data/index.js';
|
||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export interface IReq_GetPublicKeyForValidation
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetPublicKeyForValidation
|
||||||
|
> {
|
||||||
|
method: 'getPublicKeyForValidation';
|
||||||
|
request: {
|
||||||
|
backendToken: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
publicKeyPem: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_PushPublicKeyForValidation
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_PushPublicKeyForValidation
|
||||||
|
> {
|
||||||
|
method: 'pushPublicKeyForValidation';
|
||||||
|
request: {
|
||||||
|
publicKeyPem: string;
|
||||||
|
};
|
||||||
|
response: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows getting or pushing a blocklist of jwt ids
|
||||||
|
*/
|
||||||
|
export interface IReq_PushOrGetJwtIdBlocklist
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_PushOrGetJwtIdBlocklist
|
||||||
|
> {
|
||||||
|
method: 'pushOrGetJwtIdBlocklist';
|
||||||
|
request: {
|
||||||
|
blockedJwtIds?: string[];
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
blockedJwtIds?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import * as data from '../data/index.js';
|
||||||
|
|
||||||
|
export interface IReq_LoginWithEmailOrUsernameAndPassword
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_LoginWithEmailOrUsernameAndPassword
|
||||||
|
> {
|
||||||
|
method: 'loginWithEmailOrUsernameAndPassword';
|
||||||
|
request: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
refreshToken?: string;
|
||||||
|
twoFaNeeded: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_LoginWithEmail
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_LoginWithEmailOrUsernameAndPassword
|
||||||
|
> {
|
||||||
|
method: 'loginWithEmail';
|
||||||
|
request: {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
testOnlyToken?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_LoginWithEmailAfterEmailTokenAquired
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_LoginWithEmailOrUsernameAndPassword
|
||||||
|
> {
|
||||||
|
method: 'loginWithEmailAfterEmailTokenAquired';
|
||||||
|
request: {
|
||||||
|
email: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* in case you authenticate with a long lived api token
|
||||||
|
*/
|
||||||
|
export interface IReq_LoginWithApiToken
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_LoginWithApiToken
|
||||||
|
> {
|
||||||
|
method: 'loginWithApiToken';
|
||||||
|
request: {
|
||||||
|
apiToken: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
jwt?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILogoutRequest
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
ILogoutRequest
|
||||||
|
> {
|
||||||
|
method: 'logout';
|
||||||
|
request: {
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
response: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_RefreshJwt
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_RefreshJwt
|
||||||
|
> {
|
||||||
|
method: 'refreshJwt';
|
||||||
|
request: {
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: data.TLoginStatus;
|
||||||
|
jwt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows the exchange between refreshToken and transferTokens
|
||||||
|
*/
|
||||||
|
export interface IReq_ExchangeRefreshTokenAndTransferToken
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_ExchangeRefreshTokenAndTransferToken
|
||||||
|
> {
|
||||||
|
method: 'exchangeRefreshTokenAndTransferToken';
|
||||||
|
request: {
|
||||||
|
transferToken?: string;
|
||||||
|
refreshToken?: string;
|
||||||
|
appData: data.IApp;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
refreshToken?: string;
|
||||||
|
transferToken?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* in case you authenticate with a long lived api token
|
||||||
|
*/
|
||||||
|
export interface IReq_ResetPassword
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_ResetPassword
|
||||||
|
> {
|
||||||
|
method: 'resetPassword';
|
||||||
|
request: {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* in cse you authenticate with a long lived api token
|
||||||
|
*/
|
||||||
|
export interface IReq_SetNewPassword
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_SetNewPassword
|
||||||
|
> {
|
||||||
|
method: 'setNewPassword';
|
||||||
|
request: {
|
||||||
|
email: string;
|
||||||
|
oldPassword?: string;
|
||||||
|
tokenArg?: string;
|
||||||
|
newPassword: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_ObtainDeviceId
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_ObtainDeviceId
|
||||||
|
> {
|
||||||
|
method: 'obtainDeviceId';
|
||||||
|
request: {};
|
||||||
|
response: {
|
||||||
|
deviceId: data.IDevice;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows attaching a device id to a login session
|
||||||
|
* to share a login session across contexts
|
||||||
|
*/
|
||||||
|
export interface IReq_AttachDeviceId
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_AttachDeviceId
|
||||||
|
> {
|
||||||
|
method: 'attachDeviceId';
|
||||||
|
request: {
|
||||||
|
jwt: string;
|
||||||
|
deviceId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
ok: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import * as data from '../data/index.js';
|
||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export interface IReq_GetOrganizationById
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetOrganizationById
|
||||||
|
> {
|
||||||
|
method: 'getOrganizationById';
|
||||||
|
request: {
|
||||||
|
jwt: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
organization: data.IOrganization;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_CreateOrganization
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_CreateOrganization
|
||||||
|
> {
|
||||||
|
method: 'createOrganization';
|
||||||
|
request: {
|
||||||
|
jwt: string;
|
||||||
|
userId: string;
|
||||||
|
organizationName: string;
|
||||||
|
organizationSlug: string;
|
||||||
|
action: 'checkAvailability' | 'manifest';
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
nameAvailable: boolean;
|
||||||
|
resultingOrganization?: data.IOrganization;
|
||||||
|
role?: data.IRole;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_UpdateOrganization
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_UpdateOrganization
|
||||||
|
> {
|
||||||
|
method: 'updateOrganization';
|
||||||
|
request: {
|
||||||
|
organization: data.IOrganization;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
organization: data.IOrganization;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import * as data from '../data/index.js';
|
||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export interface IReq_GetPlansForOrganizationId
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetPlansForOrganizationId
|
||||||
|
> {
|
||||||
|
method: 'getBillingPlansForOrganizationId';
|
||||||
|
request: {
|
||||||
|
jwt: string;
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
billingPlans: data.IBillingPlan[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
import { type IUser } from '../data/index.js';
|
||||||
|
|
||||||
|
export interface IReq_FirstRegistration
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_FirstRegistration
|
||||||
|
> {
|
||||||
|
method: 'firstRegistrationRequest';
|
||||||
|
request: {
|
||||||
|
email: string;
|
||||||
|
productSlugOfInterest: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
testOnlyToken?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_AfterRegistrationEmailClicked
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_AfterRegistrationEmailClicked
|
||||||
|
> {
|
||||||
|
method: 'afterRegistrationEmailClicked';
|
||||||
|
request: {
|
||||||
|
/**
|
||||||
|
* the token that has been sent with the registation email to verify access
|
||||||
|
*/
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
/**
|
||||||
|
* the email thats associated with the given request token
|
||||||
|
*/
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_SetDataForRegistration
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_SetDataForRegistration
|
||||||
|
> {
|
||||||
|
method: 'setDataForRegistration';
|
||||||
|
request: {
|
||||||
|
token: string;
|
||||||
|
userData: IUser['data'];
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be used to verify a mobile number for an verifcation
|
||||||
|
*/
|
||||||
|
export interface IReq_MobileVerificationForRegistration
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_MobileVerificationForRegistration
|
||||||
|
> {
|
||||||
|
method: 'mobileVerificationForRegistration';
|
||||||
|
request: {
|
||||||
|
token: string;
|
||||||
|
mobileNumber?: string;
|
||||||
|
verificationCode?: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
messageSent?: boolean;
|
||||||
|
verficationCodeOk?: boolean;
|
||||||
|
testOnlySmsCode?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_FinishRegistration
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_FinishRegistration
|
||||||
|
> {
|
||||||
|
method: 'finishRegistration';
|
||||||
|
request: {
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
status: 'ok' | 'not ok';
|
||||||
|
userData?: IUser['data'];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import * as data from '../data/index.js';
|
||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export interface IReq_GetUserData
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetUserData
|
||||||
|
> {
|
||||||
|
method: 'getUserData';
|
||||||
|
request: {
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
jwt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_SetUserData
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_SetUserData
|
||||||
|
> {
|
||||||
|
method: 'setUserData';
|
||||||
|
request: {
|
||||||
|
refreshToken: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
oneTimeTransferCode: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_SuspendUser
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_SuspendUser
|
||||||
|
> {
|
||||||
|
method: 'suspendUser';
|
||||||
|
request: {
|
||||||
|
jwt: string;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
publicKeyPem: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDeleteSuspendedUser
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IDeleteSuspendedUser
|
||||||
|
> {
|
||||||
|
method: 'deleteSuspendedUser';
|
||||||
|
request: {
|
||||||
|
backendToken: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
ok: boolean;
|
||||||
|
errorText?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReq_GetRolesAndOrganizationsForUserId
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTR<
|
||||||
|
plugins.typedRequestInterfaces.ITypedRequest,
|
||||||
|
IReq_GetRolesAndOrganizationsForUserId
|
||||||
|
> {
|
||||||
|
method: 'getRolesAndOrganizationsForUserId';
|
||||||
|
request: {
|
||||||
|
jwt: string;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
roles: data.IRole[];
|
||||||
|
organizations: data.IOrganization[];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import * as plugins from '../loint-reception.plugins.js';
|
||||||
|
|
||||||
|
export interface ITag_LolePubapi
|
||||||
|
extends plugins.typedRequestInterfaces.implementsTag<
|
||||||
|
plugins.typedRequestInterfaces.ITag,
|
||||||
|
ITag_LolePubapi
|
||||||
|
> {
|
||||||
|
name: 'lole-reception';
|
||||||
|
payload: {
|
||||||
|
backendToken: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
import { IdpState } from '../idp.state.js';
|
||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
DeesElement,
|
||||||
|
property,
|
||||||
|
state,
|
||||||
|
html,
|
||||||
|
cssManager,
|
||||||
|
unsafeCSS,
|
||||||
|
css,
|
||||||
|
type TemplateResult,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('idp-registration-stepper')
|
||||||
|
export class IdpRegistrationStepper extends DeesElement {
|
||||||
|
public idpState = IdpState.getSingletonInstance();
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private usedSubTemplate: TemplateResult;
|
||||||
|
|
||||||
|
@state()
|
||||||
|
private storedData = {
|
||||||
|
validationTokenUrlParam: 'string',
|
||||||
|
email: '',
|
||||||
|
refreshToken: '',
|
||||||
|
jwt: '',
|
||||||
|
oneTimeTransferToken: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
color: ${cssManager.bdTheme('#333', '#fff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<style></style>
|
||||||
|
<div class="main">
|
||||||
|
${this.usedSubTemplate
|
||||||
|
? this.usedSubTemplate
|
||||||
|
: html`<dees-spinner size="60"></dees-spinner>`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async firstUpdated() {
|
||||||
|
await this.domtoolsPromise;
|
||||||
|
this.domtools.router.on(`/finishregistration`, async (routeArg) => {
|
||||||
|
this.storedData.validationTokenUrlParam = routeArg.queryParams.validationtoken;
|
||||||
|
if (!this.storedData.validationTokenUrlParam) {
|
||||||
|
this.usedSubTemplate = html`
|
||||||
|
You need a validation token, but we couldn't find one. Please contact workspace.global support.
|
||||||
|
`;
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(5000);
|
||||||
|
this.usedSubTemplate = html` Redirecting you to workspace.global support... `;
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(2000);
|
||||||
|
window.location.href = 'https://support.workspace.global';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// lets verify the info;
|
||||||
|
let tokenErrorMessage: string;
|
||||||
|
const resAfterRegEmailClicked =
|
||||||
|
await this.idpState.idpClient.requests.afterRegistrationEmailClicked
|
||||||
|
.fire({
|
||||||
|
token: this.storedData.validationTokenUrlParam,
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
(
|
||||||
|
err: typeof DeesElement['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
|
||||||
|
) => {
|
||||||
|
tokenErrorMessage = err.errorText;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!resAfterRegEmailClicked || !resAfterRegEmailClicked.email) {
|
||||||
|
this.usedSubTemplate = html`
|
||||||
|
the supplied validation token does not match any registration sessions.<br />
|
||||||
|
${tokenErrorMessage ? html`Reason: ${tokenErrorMessage}` : null}
|
||||||
|
`;
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(5000);
|
||||||
|
this.usedSubTemplate = html`redirecting you for further support... `;
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(1000);
|
||||||
|
window.location.href = 'https://support.workspace.global';
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.storedData.email = resAfterRegEmailClicked.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lets continue with UI
|
||||||
|
this.usedSubTemplate = html`<dees-stepper
|
||||||
|
.steps=${[
|
||||||
|
{
|
||||||
|
title: 'What is your name?',
|
||||||
|
content: html`
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-text
|
||||||
|
key="email"
|
||||||
|
label="Your Email"
|
||||||
|
value="${this.storedData.email}"
|
||||||
|
disabled
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-input-text key="firstName" required label="First Name"></dees-input-text>
|
||||||
|
<dees-input-text key="lastName" required label="Last Name"></dees-input-text>
|
||||||
|
<dees-form-submit>Next</dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
`,
|
||||||
|
validationFunc: async (stepperArg, elementArg) => {
|
||||||
|
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
|
||||||
|
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
|
||||||
|
const response = await this.idpState.idpClient.requests.setData
|
||||||
|
.fire({
|
||||||
|
token: this.storedData.validationTokenUrlParam,
|
||||||
|
userData: {
|
||||||
|
name: `${eventArg.detail.data.firstName} ${eventArg.detail.data.lastName}`,
|
||||||
|
connectedOrgs: null,
|
||||||
|
email: null,
|
||||||
|
status: null,
|
||||||
|
username: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
(
|
||||||
|
errArg: typeof DeesElement['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
|
||||||
|
) => {
|
||||||
|
deesForm.setStatus('error', errArg.errorText);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
deesForm.setStatus('success', 'ok!');
|
||||||
|
stepperArg.goNext();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onReturnToStepFunc: async (stepperArg, stepElementArg) => {
|
||||||
|
const deesForm = stepElementArg.querySelector('dees-form');
|
||||||
|
deesForm.setStatus('normal', 'Edit and Next');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'What is your mobile number?',
|
||||||
|
content: html`
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-text
|
||||||
|
key="mobileNumber"
|
||||||
|
required
|
||||||
|
label="Your Mobile Number"
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-form-submit>Next</dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
`,
|
||||||
|
validationFunc: async (stepperArg, elementArg) => {
|
||||||
|
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
|
||||||
|
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
|
||||||
|
const response = await this.idpState.idpClient.requests.mobileNumberVerification
|
||||||
|
.fire({
|
||||||
|
token: this.storedData.validationTokenUrlParam,
|
||||||
|
mobileNumber: eventArg.detail.data.mobileNumber,
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
(
|
||||||
|
errArg: typeof DeesElement['prototype']['domtools']['convenience']['typedrequest']['TypedResponseError']['prototype']
|
||||||
|
) => {
|
||||||
|
deesForm.setStatus('error', errArg.errorText);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
deesForm.setStatus('success', 'ok!');
|
||||||
|
stepperArg.goNext();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onReturnToStepFunc: async (stepperArg, stepElementArg) => {
|
||||||
|
const deesForm = stepElementArg.querySelector('dees-form');
|
||||||
|
deesForm.setStatus('normal', 'Edit and Next');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'What is the Verification Code?',
|
||||||
|
content: html`
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-text
|
||||||
|
key="verificationCode"
|
||||||
|
required
|
||||||
|
label="Verification Code"
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-form-submit>Next</dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
`,
|
||||||
|
validationFunc: async (stepperArg, elementArg) => {
|
||||||
|
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
|
||||||
|
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
|
||||||
|
const response = await this.idpState.idpClient.requests.mobileNumberVerification.fire({
|
||||||
|
token: this.storedData.validationTokenUrlParam,
|
||||||
|
verificationCode: eventArg.detail.data.verificationCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.verficationCodeOk) {
|
||||||
|
deesForm.setStatus('success', 'ok!');
|
||||||
|
stepperArg.goNext();
|
||||||
|
} else {
|
||||||
|
deesForm.setStatus('error', 'wrong code!');
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(3000);
|
||||||
|
deesForm.setStatus('normal', 'Retry And Next!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onReturnToStepFunc: async (stepperArg, stepElementArg) => {
|
||||||
|
stepperArg.goBack();
|
||||||
|
const deesForm = stepElementArg.querySelector('dees-form');
|
||||||
|
deesForm.setStatus('normal', 'Next');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Create a secure password:',
|
||||||
|
content: html`
|
||||||
|
<dees-form>
|
||||||
|
<dees-input-text
|
||||||
|
key="password"
|
||||||
|
required
|
||||||
|
label="Your New Secure Password"
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-form-submit>Next</dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
`,
|
||||||
|
validationFunc: async (stepperArg, elementArg) => {
|
||||||
|
const deesForm: plugins.deesCatalog.DeesForm = elementArg.querySelector('dees-form');
|
||||||
|
deesForm.addEventListener('formData', async (eventArg: CustomEvent) => {
|
||||||
|
const response = await this.idpState.idpClient.requests.setData.fire({
|
||||||
|
token: this.storedData.validationTokenUrlParam,
|
||||||
|
userData: {
|
||||||
|
username: null,
|
||||||
|
email: null,
|
||||||
|
name: null,
|
||||||
|
connectedOrgs: null,
|
||||||
|
status: null,
|
||||||
|
password: eventArg.detail.data.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const finishRegistrationResponse =
|
||||||
|
await this.idpState.idpClient.requests.finishRegistration.fire({
|
||||||
|
token: this.storedData.validationTokenUrlParam,
|
||||||
|
});
|
||||||
|
deesForm.setStatus('pending', 'User created!');
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(500);
|
||||||
|
deesForm.setStatus('pending', 'Obtaining Refresh Token...');
|
||||||
|
const loginResponse = await this.idpState.idpClient.requests.loginWithUserNameAndPassword.fire(
|
||||||
|
{
|
||||||
|
username: this.storedData.email,
|
||||||
|
password: eventArg.detail.data.password,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.storedData.refreshToken = loginResponse.refreshToken;
|
||||||
|
|
||||||
|
deesForm.setStatus('pending', 'Obtaining JWT...');
|
||||||
|
const jwtResponse = await this.idpState.idpClient.requests.obtainJwt.fire({
|
||||||
|
refreshToken: this.storedData.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
deesForm.setStatus('pending', 'Obtaining Transfer Token...');
|
||||||
|
await this.idpState.idpClient.setJwt(jwtResponse.jwt);
|
||||||
|
await this.idpState.idpClient.getTransferTokenAndSwitchToLocation('https://sso.workspace.global/afterregistration');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] as plugins.deesCatalog.IStep[]}
|
||||||
|
></dees-stepper>`;
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(100);
|
||||||
|
});
|
||||||
|
this.domtools.router.on('/', async () => {
|
||||||
|
this.usedSubTemplate = html`Hm, this is app is not meant for what you are trying to do :) `;
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(2000);
|
||||||
|
this.usedSubTemplate = html`Redirecting you now...`;
|
||||||
|
window.location.href = `https://workspace.global`;
|
||||||
|
});
|
||||||
|
this.domtools.router._handleRouteState();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,264 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
DeesElement,
|
||||||
|
property,
|
||||||
|
html,
|
||||||
|
type TemplateResult,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
query,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
import { commitinfo } from '../../dist_ts/00_commitinfo_data.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'wg-logincontainer': IdpLogincontainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('idp-logincontainer')
|
||||||
|
export class IdpLogincontainer extends DeesElement {
|
||||||
|
public static demo = () => html`<wg-logincontainer></wg-logincontainer>`;
|
||||||
|
|
||||||
|
@query('.loginPromptContainer')
|
||||||
|
loginPromptContainer: HTMLDivElement;
|
||||||
|
|
||||||
|
@query('.loginManagerContainer')
|
||||||
|
loginManagerContainer: HTMLDivElement
|
||||||
|
|
||||||
|
@query('.transferManagerContainer')
|
||||||
|
transferManagerContainer: HTMLDivElement
|
||||||
|
|
||||||
|
public receptionClient = new plugins.idpClient.IdpClient('https://reception.lossless.one:443', {
|
||||||
|
appUrl: 'https://sso.workspace.global/',
|
||||||
|
description: 'the central sso app for workspace.global',
|
||||||
|
logoUrl: 'https://assetbroker.lossless.one/some',
|
||||||
|
name: 'sso.workspace.global',
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.2s;
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
transform: translate3d(0px, 20px, 0px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginPromptContainer.show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
transform: translate3d(0px, 0px, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginManagerContainer.show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
transform: translate3d(0px, 0px, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transferManagerContainer.show {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
transform: translate3d(0px, 0px, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginblock {
|
||||||
|
max-width: 520px;
|
||||||
|
flex-grow: 1;
|
||||||
|
transform: translate3d(0px, 0px, 0px);
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
background: ${cssManager.bdTheme('#ffffff', '#181818')};
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('#ffffff', '#333333')};
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 130px;
|
||||||
|
min-height: 34.9px;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
filter: ${cssManager.bdTheme('invert(1)', '')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.legalinfo {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: ${cssManager.bdTheme('#666', '#ccc')};
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
background: ${cssManager.bdTheme('#f5f5f5', '#111')};
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('#ccc', '#222222')};
|
||||||
|
font-family: 'Hubot Sans';
|
||||||
|
color: ${cssManager.bdTheme('#666', '#888')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.legalinfo a {
|
||||||
|
color: ${cssManager.bdTheme('#666', '#ccc')};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="mainContainer loginPromptContainer">
|
||||||
|
<div class="loginblock">
|
||||||
|
<img
|
||||||
|
src="https://assetbroker.lossless.one/brandfiles/00general/plain_workspaceglobal.svg"
|
||||||
|
/>
|
||||||
|
<wg-loginprompt></wg-loginprompt>
|
||||||
|
<div class="legalinfo">
|
||||||
|
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
|
||||||
|
| <a href="https://task.vc/" target="_blank">Company Website</a>
|
||||||
|
| <a href="https://support.task.vc/" target="_blank">Support</a>
|
||||||
|
| SSO v${commitinfo.version}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mainContainer loginManagerContainer">
|
||||||
|
<div class="loginblock">
|
||||||
|
<img
|
||||||
|
src="https://assetbroker.lossless.one/brandfiles/00general/plain_workspaceglobal.svg"
|
||||||
|
/>
|
||||||
|
<div class="legalinfo">
|
||||||
|
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
|
||||||
|
| <a href="https://task.vc/" target="_blank">Company Website</a>
|
||||||
|
| <a href="https://support.task.vc/" target="_blank">Support</a>
|
||||||
|
| SSO v${commitinfo.version}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mainContainer transferManagerContainer">
|
||||||
|
<div class="loginblock">
|
||||||
|
<img
|
||||||
|
src="https://assetbroker.lossless.one/brandfiles/00general/plain_workspaceglobal.svg"
|
||||||
|
/>
|
||||||
|
<wg-transfermanager></wg-transfermanager>
|
||||||
|
<div class="legalinfo">
|
||||||
|
<a href="https://legal.task.vc/" target="_blank">Legal Info</a>
|
||||||
|
| <a href="https://task.vc/" target="_blank">Company Website</a>
|
||||||
|
| <a href="https://support.task.vc/" target="_blank">Support</a>
|
||||||
|
| SSO v${commitinfo.version}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async showComponent(componentNameArg: 'loginPrompt' | 'loginManager' | 'transferManager') {
|
||||||
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
|
const containerItems: HTMLDivElement[] = [
|
||||||
|
this.loginPromptContainer,
|
||||||
|
this.loginManagerContainer,
|
||||||
|
this.transferManagerContainer,
|
||||||
|
];
|
||||||
|
const show = async (itemArg: HTMLDivElement) => {
|
||||||
|
for (const containerItem of containerItems) {
|
||||||
|
if (containerItem !== itemArg) {
|
||||||
|
containerItem.classList.remove('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await domtoolsInstance.convenience.smartdelay.delayFor(200);
|
||||||
|
itemArg.classList.add('show');
|
||||||
|
await domtoolsInstance.convenience.smartdelay.delayFor(200);
|
||||||
|
};
|
||||||
|
switch (componentNameArg) {
|
||||||
|
case 'loginPrompt':
|
||||||
|
await show(this.loginPromptContainer);
|
||||||
|
break;
|
||||||
|
case 'loginManager':
|
||||||
|
await show(this.loginManagerContainer);
|
||||||
|
break;
|
||||||
|
case 'transferManager':
|
||||||
|
await show(this.transferManagerContainer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async determineNextAction() {
|
||||||
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
|
let action: plugins.idpInterfaces.data.TLoginAction;
|
||||||
|
if (domtoolsInstance.router.queryParams.getQueryParam('action')) {
|
||||||
|
action = domtoolsInstance.router.queryParams.getQueryParam('action');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.location.pathname === '/afterregistration') {
|
||||||
|
await this.domtools.convenience.smartdelay.delayFor(1000);
|
||||||
|
await this.receptionClient.determineLoginStatus();
|
||||||
|
await this.receptionClient.getTransferTokenAndSwitchToLocation('https://account.workspace.global')
|
||||||
|
} else if (!(await this.receptionClient.determineLoginStatus()) && action === 'login') {
|
||||||
|
this.showComponent('loginPrompt');
|
||||||
|
} else if ((await this.receptionClient.determineLoginStatus()) && action === 'login') {
|
||||||
|
await this.showComponent('transferManager');
|
||||||
|
const wgTransferManager = this.shadowRoot.querySelector('wg-transfermanager');
|
||||||
|
await wgTransferManager.handleTransfer();
|
||||||
|
} else if ((await this.receptionClient.determineLoginStatus()) && action === 'manage') {
|
||||||
|
this.showComponent('loginManager');
|
||||||
|
} else if (action === 'logout') {
|
||||||
|
console.log('logging out, since requested action is "logout"');
|
||||||
|
await this.receptionClient.logout();
|
||||||
|
} else {
|
||||||
|
this.showComponent('loginPrompt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async firstUpdated() {
|
||||||
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
|
await domtoolsInstance.convenience.smartdelay.delayFor(0);
|
||||||
|
console.log(`your are loggedin: ${await await this.receptionClient.determineLoginStatus()}`);
|
||||||
|
let appData: plugins.idpInterfaces.data.IApp;
|
||||||
|
|
||||||
|
if (domtoolsInstance.router.queryParams.getQueryParam('appdata')) {
|
||||||
|
appData = domtoolsInstance.convenience.smartjson.parseBase64(
|
||||||
|
domtoolsInstance.router.queryParams.getQueryParam('appdata')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wgLogin = this.shadowRoot.querySelector('wg-loginprompt');
|
||||||
|
const wgTransferManager = this.shadowRoot.querySelector('wg-transfermanager');
|
||||||
|
wgLogin.appData = appData;
|
||||||
|
wgTransferManager.appData = appData;
|
||||||
|
|
||||||
|
await this.determineNextAction();
|
||||||
|
wgLogin.jwtObserable.subscribe({
|
||||||
|
next: async (jwtArg) => {
|
||||||
|
console.log('loggedIn');
|
||||||
|
await this.receptionClient.storeJwt(jwtArg);
|
||||||
|
await this.determineNextAction();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
wgLogin.dispatchJwt();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
DeesElement,
|
||||||
|
property,
|
||||||
|
html,
|
||||||
|
type TemplateResult,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
state,
|
||||||
|
domtools,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
// third party catalogs
|
||||||
|
import '@uptimelink/webwidget';
|
||||||
|
|
||||||
|
import '@design.estate/dees-catalog';
|
||||||
|
import { DeesForm, DeesFormSubmit, DeesInputText } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'wg-loginprompt': WgLogin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('wg-loginprompt')
|
||||||
|
export class WgLogin extends DeesElement {
|
||||||
|
public static demo = () => html`<wg-loginprompt></wg-loginprompt>`;
|
||||||
|
public static receptionUrl = 'https://reception.lossless.one/typedrequest';
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public activePane: 'login' | 'register' = 'login';
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public productOfInterest: string;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
jwt: string;
|
||||||
|
|
||||||
|
@property({
|
||||||
|
reflect: true,
|
||||||
|
})
|
||||||
|
appData: plugins.idpInterfaces.data.IApp;
|
||||||
|
|
||||||
|
public jwtObserable = new domtools.plugins.smartrx.rxjs.Subject<string>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
domtools.elementBasic.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
color: ${cssManager.bdTheme('#333333', '#ffffff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box.active {
|
||||||
|
opacity: 1 !important;
|
||||||
|
height: 360px;
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loginbox {
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerbox {
|
||||||
|
}
|
||||||
|
|
||||||
|
.boxcontent {
|
||||||
|
margin: 0px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
text-align: center;
|
||||||
|
padding: 32px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerButton {
|
||||||
|
display: block;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
will-change: transform;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerButton:hover {
|
||||||
|
color: #fff;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="loginbox box ${this.activePane === 'login' ? 'active' : ''}">
|
||||||
|
<div class="boxcontent">
|
||||||
|
<dees-form
|
||||||
|
id="loginForm"
|
||||||
|
@formData="${(eventArg) => {
|
||||||
|
this.login({
|
||||||
|
emailAddress: eventArg.detail.data.emailAddress,
|
||||||
|
passwordArg: eventArg.detail.data.password,
|
||||||
|
});
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<dees-input-text
|
||||||
|
id="loginEmailInput"
|
||||||
|
.required=${true}
|
||||||
|
key="emailAddress"
|
||||||
|
label="Email-Address or Username"
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-input-text
|
||||||
|
.id=${'loginPasswordInput'}
|
||||||
|
.key=${'password'}
|
||||||
|
.label=${'Password'}
|
||||||
|
.isPasswordBool=${true}
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-form-submit id="loginSubmitButton"></dees-form-submit>
|
||||||
|
<div class="info">
|
||||||
|
You'll go here: ${this.appData ? html`${this.appData.appUrl}` : html``}
|
||||||
|
<p><span class="registerButton" @click=${() => {this.activePane = 'register'}}>You can also register for a new account.</span></p>
|
||||||
|
</div>
|
||||||
|
</dees-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="registerbox box ${this.activePane === 'register' ? 'active' : ''}">
|
||||||
|
<div class="boxcontent">
|
||||||
|
<dees-form
|
||||||
|
id="registrationForm"
|
||||||
|
@formData="${(eventArg) => {
|
||||||
|
this.register({
|
||||||
|
emailAddress: eventArg.detail.data.emailAddress,
|
||||||
|
});
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<dees-input-text
|
||||||
|
.required=${true}
|
||||||
|
key="emailAddress"
|
||||||
|
label="Email-Address"
|
||||||
|
></dees-input-text>
|
||||||
|
<dees-input-checkbox .label="${'Agree to the Terms and Conditions'}"></dees-input-checkbox>
|
||||||
|
<dees-form-submit>Send Verification Email</dees-form-submit>
|
||||||
|
<div class="info">
|
||||||
|
Already have an account?
|
||||||
|
<p><span class="registerButton" @click=${() => {this.activePane = 'login'}}>Login instead.</span></p>
|
||||||
|
</div>
|
||||||
|
</dees-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async firstUpdated() {
|
||||||
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
|
const loginForm: DeesForm = this.shadowRoot.querySelector('#loginForm');
|
||||||
|
const loginPasswordInput: DeesInputText = loginForm.querySelector('#loginPasswordInput');
|
||||||
|
const loginSubmitButton: DeesFormSubmit = loginForm.querySelector('#loginSubmitButton');
|
||||||
|
const setButtonText = async () => {
|
||||||
|
if (loginPasswordInput.value) {
|
||||||
|
console.log('updating text of loginprompt.')
|
||||||
|
loginSubmitButton.text = 'Login';
|
||||||
|
} else {
|
||||||
|
loginSubmitButton.text = 'Send magic link (or enter password)';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loginForm.changeSubject.subscribe(() => {
|
||||||
|
console.log(`checking button text ${loginPasswordInput.value}`);
|
||||||
|
setButtonText();
|
||||||
|
});
|
||||||
|
setButtonText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private login = async (valueArg: { emailAddress: string; passwordArg: string }) => {
|
||||||
|
// lets define the needed requests
|
||||||
|
const loginForm: DeesForm = this.shadowRoot.querySelector('#loginForm');
|
||||||
|
const loginRequestWithUsernameAndPassword =
|
||||||
|
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmailOrUsernameAndPassword>(
|
||||||
|
WgLogin.receptionUrl,
|
||||||
|
'loginWithEmailOrUsernameAndPassword'
|
||||||
|
);
|
||||||
|
const loginRequestWithEmail =
|
||||||
|
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_LoginWithEmail>(
|
||||||
|
WgLogin.receptionUrl,
|
||||||
|
'loginWithEmail'
|
||||||
|
);
|
||||||
|
|
||||||
|
// lets do the actual logging in
|
||||||
|
if (valueArg.emailAddress && valueArg.passwordArg) {
|
||||||
|
loginForm.setStatus('pending', 'logging in...');
|
||||||
|
const response = await loginRequestWithUsernameAndPassword
|
||||||
|
.fire({
|
||||||
|
username: valueArg.emailAddress, // TODO: rename to emailAddress
|
||||||
|
password: valueArg.passwordArg,
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loginForm.setStatus('error', 'could not log you in. Try Again!');
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
if (!response) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.refreshToken) {
|
||||||
|
loginForm.setStatus('pending', 'obtained refreshToken...');
|
||||||
|
const jwt = await this.handleRefreshToken(response.refreshToken, 0);
|
||||||
|
if (jwt) {
|
||||||
|
loginForm.setStatus('success', 'obtained jwt.');
|
||||||
|
} else {
|
||||||
|
loginForm.setStatus('error', 'something went wrong');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
} else if (valueArg.emailAddress && !valueArg.passwordArg) {
|
||||||
|
loginForm.setStatus('pending', 'sending magic link...');
|
||||||
|
const response = await loginRequestWithEmail.fire({
|
||||||
|
email: valueArg.emailAddress,
|
||||||
|
});
|
||||||
|
if (response.status === 'ok') {
|
||||||
|
loginForm.setStatus('success', 'Please check your email!');
|
||||||
|
}
|
||||||
|
console.log(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private register = async (valueArg: { emailAddress: string }) => {
|
||||||
|
const registrationForm: DeesForm = this.shadowRoot.querySelector('#registrationForm');
|
||||||
|
registrationForm.setStatus('pending', 'registering...');
|
||||||
|
const firstSignupRequest =
|
||||||
|
new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_FirstRegistration>(
|
||||||
|
WgLogin.receptionUrl,
|
||||||
|
'firstRegistrationRequest'
|
||||||
|
);
|
||||||
|
const response = await firstSignupRequest
|
||||||
|
.fire({
|
||||||
|
email: valueArg.emailAddress,
|
||||||
|
productSlugOfInterest: this.productOfInterest,
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
registrationForm.setStatus('error', err.message);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (response.status === 'ok') {
|
||||||
|
registrationForm.setStatus('success', 'Please check your email!');
|
||||||
|
}
|
||||||
|
console.log(response);
|
||||||
|
};
|
||||||
|
|
||||||
|
public async dispatchJwt(jwtArg?: string) {
|
||||||
|
if (jwtArg !== undefined) {
|
||||||
|
console.log(`dispatching jwt from loginprompt.`);
|
||||||
|
this.jwt = jwtArg;
|
||||||
|
await domtools.plugins.smartdelay.delayFor(200);
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('leleLoginGotJwt', {
|
||||||
|
detail: {
|
||||||
|
jwt: this.jwt,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.jwtObserable.next(this.jwt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleRefreshToken(refreshTokenArg: string, delayDispatchMillisArg = 0) {
|
||||||
|
// a refreshToken binds dierctly to a session.
|
||||||
|
// the refresh token is used on a continuous basis to get fresh and short-lived jwts
|
||||||
|
const refreshJwt = new domtools.TypedRequest<plugins.idpInterfaces.request.IReq_RefreshJwt>(
|
||||||
|
WgLogin.receptionUrl,
|
||||||
|
'refreshJwt'
|
||||||
|
);
|
||||||
|
const responseJwt = await refreshJwt.fire({
|
||||||
|
refreshToken: refreshTokenArg,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (responseJwt.jwt) {
|
||||||
|
this.domtools.convenience.smartdelay.delayFor(delayDispatchMillisArg).then(() => {
|
||||||
|
this.dispatchJwt(responseJwt.jwt);
|
||||||
|
});
|
||||||
|
return responseJwt.jwt;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
DeesElement,
|
||||||
|
property,
|
||||||
|
html,
|
||||||
|
type TemplateResult,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
state,
|
||||||
|
domtools,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'wg-transfermanager': WgTransfermanager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('wg-transfermanager')
|
||||||
|
export class WgTransfermanager extends DeesElement {
|
||||||
|
|
||||||
|
public appData: plugins.idpInterfaces.data.IApp;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
max-width: 520px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
color: ${cssManager.bdTheme('#333333', '#ffffff')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
height: 56px;
|
||||||
|
background: ${cssManager.bdTheme('#ffffff', '#181818')};
|
||||||
|
border-top: 1px solid ${cssManager.bdTheme('#ffffff', '#333333')};
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="box">
|
||||||
|
transfering
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleTransfer() {
|
||||||
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
|
const box = this.shadowRoot.querySelector('.box');
|
||||||
|
const receptionClient = new plugins.idpClient.IdpClient(`https://reception.lossless.one:443`, this.appData);
|
||||||
|
const transferToken = await receptionClient.getTransferToken();
|
||||||
|
box.textContent = 'got transfer token...';
|
||||||
|
console.log(this.appData.appUrl);
|
||||||
|
const redirectUrl = domtoolsInstance.convenience.smarturl.Smarturl.createFromUrl(this.appData.appUrl, {
|
||||||
|
searchParams: {
|
||||||
|
'transfertoken': transferToken,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
box.textContent = `redirecting to app...`;
|
||||||
|
window.location.href = redirectUrl.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
customElement,
|
||||||
|
DeesElement,
|
||||||
|
property,
|
||||||
|
html,
|
||||||
|
cssManager,
|
||||||
|
unsafeCSS,
|
||||||
|
css,
|
||||||
|
type TemplateResult,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('idp-welcome')
|
||||||
|
export class IdpWelcome extends DeesElement {
|
||||||
|
@property()
|
||||||
|
public someProperty = 'someProperty';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: 'Geist Sans';
|
||||||
|
}
|
||||||
|
:host([hidden]) {
|
||||||
|
font-family: 'Cal Sans';
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing:0.0125em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textbox {
|
||||||
|
margin: 24px auto;
|
||||||
|
width: 500px;
|
||||||
|
background: #111111;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textbox dees-button {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
|
||||||
|
public render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<style></style>
|
||||||
|
<h1>idp.global</h1>
|
||||||
|
<div class="textbox">
|
||||||
|
idp.global is a Open Source identity provider for the world wide web. You can get the code if you want to improve it.
|
||||||
|
<dees-button @click=${() => {
|
||||||
|
window.location.href = 'https://code.foss.global/idp.global/idp.global';
|
||||||
|
}}>Get the code</dees-button>
|
||||||
|
</div>
|
||||||
|
<div class="textbox">
|
||||||
|
Do you want to sign in or register?
|
||||||
|
<dees-button @click=${() => {}}>Sign In</dees-button>
|
||||||
|
<dees-button @click=${() => {}}>Register</dees-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class IdpState {
|
||||||
|
// STATIC
|
||||||
|
public static getSingletonInstance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new IdpState();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static instance: IdpState;
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
public receptionUrl = 'https://reception.lossless.one/typedrequest';
|
||||||
|
public idpClient = new plugins.idpClient.IdpClient(this.receptionUrl);
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import * as serviceworker from '@api.global/typedserver/web_serviceworker_client';
|
||||||
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
|
|
||||||
|
import { html, render } from '@design.estate/dees-element';
|
||||||
|
import { IdpWelcome } from './elements/idp-welcome.js';
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
const domtoolsInstance = await domtools.DomTools.setupDomTools();
|
||||||
|
domtools.elementBasic.setup();
|
||||||
|
domtoolsInstance.setWebsiteInfo({
|
||||||
|
metaObject: {
|
||||||
|
title: 'idp.global',
|
||||||
|
description:
|
||||||
|
'the code that runs idp.global',
|
||||||
|
canonicalDomain: 'https://idp.global',
|
||||||
|
ldCompany: {
|
||||||
|
name: 'Task Venture Capital GmbH',
|
||||||
|
status: 'active',
|
||||||
|
contact: {
|
||||||
|
address: {
|
||||||
|
name: 'Task Venture Capital GmbH',
|
||||||
|
city: 'Grasberg',
|
||||||
|
country: 'Germany',
|
||||||
|
houseNumber: '24',
|
||||||
|
postalCode: '28879',
|
||||||
|
streetName: 'Eickedorfer Vorweide',
|
||||||
|
},
|
||||||
|
description: 'work',
|
||||||
|
name: 'Task Venture Capital GmbH',
|
||||||
|
type: 'company',
|
||||||
|
facebookUrl: 'https://www.facebook.com/undefined variable',
|
||||||
|
twitterUrl: 'https://twitter.com/undefined variable',
|
||||||
|
website: 'https://Task Venture Capital GmbH',
|
||||||
|
phone: '+49 421 16767 548',
|
||||||
|
},
|
||||||
|
closedDate: null,
|
||||||
|
foundedDate: {
|
||||||
|
day: 1,
|
||||||
|
month: 1,
|
||||||
|
year: 2014,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const serviceWorker = await serviceworker.getServiceworkerClient();
|
||||||
|
|
||||||
|
const mainTemplate = html`
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
--background-accent: #303f9f;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<idp-welcome></idp-welcome>
|
||||||
|
`;
|
||||||
|
|
||||||
|
render(mainTemplate, document.body);
|
||||||
|
};
|
||||||
|
|
||||||
|
run();
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// node native
|
||||||
|
|
||||||
|
// project native
|
||||||
|
import * as idpInterfaces from '../dist_ts_interfaces/index.js';
|
||||||
|
import * as leleReceptionclient from '../dist_ts_idpclient/index.js';
|
||||||
|
|
||||||
|
export { idpInterfaces, leleReceptionclient as idpClient };
|
||||||
|
|
||||||
|
// @api.global scope
|
||||||
|
import * as typedrequest from '@api.global/typedrequest';
|
||||||
|
|
||||||
|
export { typedrequest };
|
||||||
|
|
||||||
|
// @design.estate scope
|
||||||
|
import * as deesCatalog from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
export { deesCatalog };
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"verbatimModuleSyntax": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist_*/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user