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