Compare commits
2 Commits
master
...
improving-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fa73bb9601 | ||
![]() |
fbfe8c63ed |
@ -1,11 +0,0 @@
|
|||||||
node_modules/
|
|
||||||
Backend/node_modules
|
|
||||||
Backend/keys
|
|
||||||
Backend/logs
|
|
||||||
Backend/lib
|
|
||||||
Backend/doc
|
|
||||||
Backend/config.ini
|
|
||||||
Frontend/build
|
|
||||||
Frontend/node_modules
|
|
||||||
FrontendLegacy/node_modules
|
|
||||||
FrontendLegacy/out
|
|
3
.drone.status
Normal file
3
.drone.status
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"url": "https://drone.hibas123.de/OpenServer/OpenAuth_server/"
|
||||||
|
}
|
24
.drone.yml
Normal file
24
.drone.yml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Build with node
|
||||||
|
image: node:12
|
||||||
|
commands:
|
||||||
|
- npm install
|
||||||
|
- npm run install
|
||||||
|
- npm run build
|
||||||
|
- name: Publish to docker
|
||||||
|
image: plugins/docker
|
||||||
|
settings:
|
||||||
|
username:
|
||||||
|
from_secret: docker_username
|
||||||
|
password:
|
||||||
|
from_secret: docker_password
|
||||||
|
auto_tag: true
|
||||||
|
repo: hibas123.azurecr.io/authserver
|
||||||
|
registry: hibas123.azurecr.io
|
||||||
|
debug: true
|
||||||
|
when:
|
||||||
|
branch: master
|
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
@ -1,37 +0,0 @@
|
|||||||
# .github/workflows/ci.yml
|
|
||||||
|
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
MY_DOCKER_USERNAME: ${{ secrets.MY_DOCKER_USERNAME }}
|
|
||||||
MY_DOCKER_PASSWORD: ${{ secrets.MY_DOCKER_PASSWORD }}
|
|
||||||
FORCE_COLOR: 1
|
|
||||||
steps:
|
|
||||||
- uses: https://github.com/earthly/actions-setup@v1
|
|
||||||
with:
|
|
||||||
version: v0.8.0
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Put back the git branch into git (Earthly uses it for tagging)
|
|
||||||
run: |
|
|
||||||
branch=""
|
|
||||||
if [ -n "$GITHUB_HEAD_REF" ]; then
|
|
||||||
branch="$GITHUB_HEAD_REF"
|
|
||||||
else
|
|
||||||
branch="${GITHUB_REF##*/}"
|
|
||||||
fi
|
|
||||||
git checkout -b "$branch" || true
|
|
||||||
- name: Docker Login
|
|
||||||
run: docker login git.hibas.dev --username "$MY_DOCKER_USERNAME" --password "$MY_DOCKER_PASSWORD"
|
|
||||||
- name: Earthly version
|
|
||||||
run: earthly --version
|
|
||||||
- name: Run build
|
|
||||||
run: earthly --push +docker-multi
|
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,7 +9,4 @@ logs/
|
|||||||
yarn-error\.log
|
yarn-error\.log
|
||||||
config.ini
|
config.ini
|
||||||
.env
|
.env
|
||||||
doc/
|
doc/
|
||||||
|
|
||||||
.yarn/cache
|
|
||||||
.yarn/install-state.gz
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "views_repo"]
|
||||||
|
path = views_repo
|
||||||
|
url = ../OpenAuth_views
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "pwa-node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"program": "${workspaceFolder}/lib/index.js",
|
||||||
|
"outFiles": ["${workspaceFolder}/**/*.js"],
|
||||||
|
"preLaunchTask": "build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
22
.vscode/tasks.json
vendored
Normal file
22
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
// Unter https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// finden Sie Informationen zum Format von "tasks.json"
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "build-ts",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": ["$tsc"],
|
||||||
|
"presentation": {
|
||||||
|
"echo": true,
|
||||||
|
"reveal": "never",
|
||||||
|
"focus": false,
|
||||||
|
"panel": "shared",
|
||||||
|
"showReuseMessage": true,
|
||||||
|
"clear": false
|
||||||
|
},
|
||||||
|
"label": "build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
541
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
541
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
File diff suppressed because one or more lines are too long
873
.yarn/releases/yarn-3.5.0.cjs
vendored
873
.yarn/releases/yarn-3.5.0.cjs
vendored
File diff suppressed because one or more lines are too long
@ -1,9 +0,0 @@
|
|||||||
nodeLinker: node-modules
|
|
||||||
|
|
||||||
npmRegistryServer: "https://npm.hibas123.de"
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.5.0.cjs
|
|
@ -1 +0,0 @@
|
|||||||
config.ini
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "openauth",
|
|
||||||
"description": "Open Auth REST API",
|
|
||||||
"title": "Open Auth REST",
|
|
||||||
"url": "/api"
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"Login": "Login",
|
|
||||||
"Username or Email": "Username or Email",
|
|
||||||
"Password": "Password",
|
|
||||||
"Next": "Next",
|
|
||||||
"Invalid code": "Invalid code",
|
|
||||||
"You are not logged in or your login is expired": "You are not logged in or your login is expired",
|
|
||||||
"User not found": "User not found",
|
|
||||||
"No special token": "No special token",
|
|
||||||
"You are not logged in or your login is expired(No special token)": "You are not logged in or your login is expired(No special token)",
|
|
||||||
"Special token invalid": "Special token invalid",
|
|
||||||
"You are not logged in or your login is expired(Special token invalid)": "You are not logged in or your login is expired(Special token invalid)",
|
|
||||||
"No login token": "No login token",
|
|
||||||
"Login token invalid": "Login token invalid",
|
|
||||||
"Authorize %s": "Authorize %s",
|
|
||||||
"By clicking on ALLOW, you allow this app to access the requested recources.": "By clicking on ALLOW, you allow this app to access the requested recources."
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@hibas123/openauth-backend",
|
|
||||||
"main": "lib/index.js",
|
|
||||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
|
||||||
"build": "run-s build-ts build-doc",
|
|
||||||
"build-doc": "apidoc -i src/ -p apidoc/",
|
|
||||||
"build-ts": "tsc",
|
|
||||||
"start": "node lib/index.js",
|
|
||||||
"dev": "nodemon -e ts --exec ts-node src/index.ts",
|
|
||||||
"format": "prettier --write \"src/**\""
|
|
||||||
},
|
|
||||||
"pipelines": {
|
|
||||||
"install": [
|
|
||||||
"cd views && npm install",
|
|
||||||
"git submodule init",
|
|
||||||
"git submodule update",
|
|
||||||
"cd views_repo && npm install"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/body-parser": "^1.19.2",
|
|
||||||
"@types/compression": "^1.7.2",
|
|
||||||
"@types/cookie-parser": "^1.4.3",
|
|
||||||
"@types/dotenv": "^8.2.0",
|
|
||||||
"@types/express": "^4.17.17",
|
|
||||||
"@types/express-session": "^1.17.7",
|
|
||||||
"@types/i18n": "^0.13.6",
|
|
||||||
"@types/ini": "^1.3.31",
|
|
||||||
"@types/jsonwebtoken": "^9.0.1",
|
|
||||||
"@types/mongodb": "^4.0.7",
|
|
||||||
"@types/node": "^18.15.11",
|
|
||||||
"@types/node-rsa": "^1.1.1",
|
|
||||||
"@types/qrcode": "^1.5.0",
|
|
||||||
"@types/speakeasy": "^2.0.7",
|
|
||||||
"@types/uuid": "^9.0.1",
|
|
||||||
"apidoc": "^0.54.0",
|
|
||||||
"concurrently": "^8.2.2",
|
|
||||||
"nodemon": "^3.0.1",
|
|
||||||
"prettier": "^2.8.7",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"typescript": "^5.0.4"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@hibas123/config": "^1.1.2",
|
|
||||||
"@hibas123/nodelogging": "^3.1.3",
|
|
||||||
"@hibas123/nodeloggingserver_client": "^1.1.2",
|
|
||||||
"@hibas123/openauth-internalapi": "workspace:^",
|
|
||||||
"@hibas123/openauth-views-v1": "workspace:^",
|
|
||||||
"@hibas123/safe_mongo": "2.0.1",
|
|
||||||
"@simplewebauthn/server": "^7.2.0",
|
|
||||||
"body-parser": "^1.20.2",
|
|
||||||
"compression": "^1.7.4",
|
|
||||||
"connect-mongo": "^5.0.0",
|
|
||||||
"cookie-parser": "^1.4.6",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"express-session": "^1.17.3",
|
|
||||||
"handlebars": "^4.7.7",
|
|
||||||
"i18n": "^0.15.1",
|
|
||||||
"ini": "^4.1.1",
|
|
||||||
"joi": "^17.11.0",
|
|
||||||
"jsonwebtoken": "^9.0.0",
|
|
||||||
"moment": "^2.29.4",
|
|
||||||
"mongodb": "^5.2.0",
|
|
||||||
"node-rsa": "^1.1.1",
|
|
||||||
"npm-run-all": "^4.1.5",
|
|
||||||
"qrcode": "^1.5.3",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
|
||||||
"speakeasy": "^2.0.0",
|
|
||||||
"u2f": "^0.1.3",
|
|
||||||
"uuid": "^9.0.1"
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@3.5.0"
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
import { Request, Response, Router } from "express";
|
|
||||||
import Stacker from "../middlewares/stacker";
|
|
||||||
import {
|
|
||||||
GetClientAuthMiddleware,
|
|
||||||
GetClientApiAuthMiddleware,
|
|
||||||
} from "../middlewares/client";
|
|
||||||
import { GetUserMiddleware } from "../middlewares/user";
|
|
||||||
import { createJWT } from "../../keys";
|
|
||||||
import Client from "../../models/client";
|
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
|
||||||
import config from "../../config";
|
|
||||||
import Mail from "../../models/mail";
|
|
||||||
|
|
||||||
const ClientRouter = Router();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @api {get} /client/user
|
|
||||||
*
|
|
||||||
* @apiDescription Can be used for simple authentication of user. It will redirect the user to the redirect URI with a very short lived jwt.
|
|
||||||
*
|
|
||||||
* @apiParam {String} redirect_uri URL to redirect to on success
|
|
||||||
* @apiParam {String} state A optional state, that will be included in the JWT and redirect_uri as parameter
|
|
||||||
*
|
|
||||||
* @apiName ClientUser
|
|
||||||
* @apiGroup client
|
|
||||||
*
|
|
||||||
* @apiPermission user_client Requires ClientID and Authenticated User
|
|
||||||
*/
|
|
||||||
ClientRouter.get(
|
|
||||||
"/user",
|
|
||||||
Stacker(
|
|
||||||
GetClientAuthMiddleware(false),
|
|
||||||
GetUserMiddleware(false, false),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
let { redirect_uri, state } = req.query;
|
|
||||||
|
|
||||||
if (redirect_uri !== req.client.redirect_url)
|
|
||||||
throw new RequestError(
|
|
||||||
"Invalid redirect URI",
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
|
|
||||||
let jwt = await createJWT(
|
|
||||||
{
|
|
||||||
client: req.client.client_id,
|
|
||||||
uid: req.user.uid,
|
|
||||||
username: req.user.username,
|
|
||||||
state: state,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expiresIn: 30,
|
|
||||||
issuer: config.core.url,
|
|
||||||
algorithm: "RS256",
|
|
||||||
subject: req.user.uid,
|
|
||||||
audience: req.client.client_id,
|
|
||||||
}
|
|
||||||
); //after 30 seconds this token is invalid
|
|
||||||
res.redirect(
|
|
||||||
redirect_uri + "?jwt=" + jwt + (state ? `&state=${state}` : "")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
ClientRouter.get(
|
|
||||||
"/account",
|
|
||||||
Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
|
|
||||||
let mails = await Promise.all(
|
|
||||||
req.user.mails.map((id) => Mail.findById(id))
|
|
||||||
);
|
|
||||||
|
|
||||||
let mail = mails.find((e) => e.primary) || mails[0];
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
user: {
|
|
||||||
username: req.user.username,
|
|
||||||
name: req.user.name,
|
|
||||||
email: mail,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @api {get} /client/featured
|
|
||||||
*
|
|
||||||
* @apiDescription Get a list of clients, that want to be featured on the home page
|
|
||||||
*
|
|
||||||
* @apiName GetFeaturedClients
|
|
||||||
* @apiGroup client
|
|
||||||
*/
|
|
||||||
ClientRouter.get(
|
|
||||||
"/featured",
|
|
||||||
Stacker(async (req: Request, res) => {
|
|
||||||
let clients = await Client.find({
|
|
||||||
featured: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
clients: clients.map(({ name, logo, website, description }) => ({
|
|
||||||
name,
|
|
||||||
logo,
|
|
||||||
website,
|
|
||||||
description,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export default ClientRouter;
|
|
@ -1,50 +0,0 @@
|
|||||||
import * as express from "express";
|
|
||||||
import AdminRoute from "./admin";
|
|
||||||
import UserRoute from "./user";
|
|
||||||
import InternalRoute from "./internal";
|
|
||||||
import ClientRouter from "./client";
|
|
||||||
import cors from "cors";
|
|
||||||
import OAuthRoute from "./oauth";
|
|
||||||
import config from "../config";
|
|
||||||
import JRPCEndpoint from "./jrpc";
|
|
||||||
|
|
||||||
const ApiRouter: express.IRouter = express.Router();
|
|
||||||
ApiRouter.use("/admin", AdminRoute);
|
|
||||||
ApiRouter.use(cors());
|
|
||||||
ApiRouter.use("/user", UserRoute);
|
|
||||||
ApiRouter.use("/internal", InternalRoute);
|
|
||||||
ApiRouter.use("/oauth", OAuthRoute);
|
|
||||||
|
|
||||||
ApiRouter.use("/client", ClientRouter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @api {post} /jrpc
|
|
||||||
* @apiName InternalJRPCEndpoint
|
|
||||||
*
|
|
||||||
* @apiGroup user
|
|
||||||
* @apiPermission none
|
|
||||||
*
|
|
||||||
* @apiErrorExample {Object} Error-Response:
|
|
||||||
{
|
|
||||||
error: [
|
|
||||||
{
|
|
||||||
message: "Some Error",
|
|
||||||
field: "username"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
status: 400
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
ApiRouter.post("/jrpc", JRPCEndpoint);
|
|
||||||
|
|
||||||
// Legacy reasons (deprecated)
|
|
||||||
ApiRouter.use("/", ClientRouter);
|
|
||||||
|
|
||||||
ApiRouter.get("/config.json", (req, res) => {
|
|
||||||
return res.json({
|
|
||||||
name: config.core.name,
|
|
||||||
url: config.core.url,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ApiRouter;
|
|
@ -1,35 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
import { GetClientAuthMiddleware } from "../middlewares/client";
|
|
||||||
import Stacker from "../middlewares/stacker";
|
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
|
||||||
import User from "../../models/user";
|
|
||||||
|
|
||||||
const PasswordAuth = Stacker(
|
|
||||||
GetClientAuthMiddleware(true, true),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
let {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
uid,
|
|
||||||
}: { username: string; password: string; uid: string } = req.body;
|
|
||||||
let query: any = { password: password };
|
|
||||||
if (username) {
|
|
||||||
query.username = username.toLowerCase();
|
|
||||||
} else if (uid) {
|
|
||||||
query.uid = uid;
|
|
||||||
} else {
|
|
||||||
throw new RequestError(
|
|
||||||
req.__("No username or uid set"),
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await User.findOne(query);
|
|
||||||
if (!user) {
|
|
||||||
res.json({ error: req.__("Password or username wrong") });
|
|
||||||
} else {
|
|
||||||
res.json({ success: true, uid: user.uid });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
export default PasswordAuth;
|
|
@ -1,38 +0,0 @@
|
|||||||
import { Format } from "@hibas123/logging";
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import { Server, } from "@hibas123/openauth-internalapi";
|
|
||||||
import { RequestObject, ResponseObject } from "@hibas123/openauth-internalapi/lib/service_base";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
import Stacker from "../middlewares/stacker";
|
|
||||||
import AccountService from "./services/account";
|
|
||||||
import LoginService from "./services/login";
|
|
||||||
import SecurityService from "./services/security";
|
|
||||||
import TFAService from "./services/twofactor";
|
|
||||||
|
|
||||||
export type SessionContext = Request;
|
|
||||||
|
|
||||||
const provider = new Server.ServiceProvider<SessionContext>();
|
|
||||||
provider.addService(new AccountService());
|
|
||||||
provider.addService(new SecurityService());
|
|
||||||
provider.addService(new TFAService());
|
|
||||||
provider.addService(new LoginService());
|
|
||||||
|
|
||||||
const JRPCEndpoint = Stacker(
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
let jrpcreq = req.body as RequestObject;
|
|
||||||
let startTime = process.hrtime.bigint();
|
|
||||||
const session = provider.getSession((data: ResponseObject) => {
|
|
||||||
let time = process.hrtime.bigint() - startTime;
|
|
||||||
let state = data.error ? Format.red(`err(${data.error.message})`) : Format.green("OK");
|
|
||||||
|
|
||||||
Logging.getChild("JRPC").log(jrpcreq.method, state, "-", (Number(time / 10000n) / 100) + "ms");
|
|
||||||
|
|
||||||
res.json(data);
|
|
||||||
}, req);
|
|
||||||
|
|
||||||
|
|
||||||
session.onMessage(jrpcreq);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default JRPCEndpoint;
|
|
@ -1,52 +0,0 @@
|
|||||||
import { Profile, ContactInfo, Gender, Server, UserRegisterInfo } from "@hibas123/openauth-internalapi";
|
|
||||||
import type { SessionContext } from "../index";
|
|
||||||
import Mail from "../../../models/mail";
|
|
||||||
import User from "../../../models/user";
|
|
||||||
import { RequireLogin } from "../../../helper/login";
|
|
||||||
|
|
||||||
export default class AccountService extends Server.AccountService<SessionContext> {
|
|
||||||
Register(regcode: string, info: UserRegisterInfo, ctx: SessionContext): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async GetProfile(ctx: SessionContext): Promise<Profile> {
|
|
||||||
if (!ctx.user) throw new Error("Not logged in");
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: ctx.user.uid,
|
|
||||||
username: ctx.user.username,
|
|
||||||
name: ctx.user.name,
|
|
||||||
birthday: ctx.user.birthday?.valueOf(),
|
|
||||||
gender: ctx.user.gender as number as Gender,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async UpdateProfile(info: Profile, ctx: SessionContext): Promise<void> {
|
|
||||||
if (!ctx.user) throw new Error("Not logged in");
|
|
||||||
|
|
||||||
ctx.user.name = info.name;
|
|
||||||
ctx.user.birthday = new Date(info.birthday);
|
|
||||||
ctx.user.gender = info.gender as number;
|
|
||||||
|
|
||||||
await User.save(ctx.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async GetContactInfos(ctx: SessionContext): Promise<ContactInfo> {
|
|
||||||
if (!ctx.user) throw new Error("Not logged in");
|
|
||||||
|
|
||||||
let mails = await Promise.all(
|
|
||||||
ctx.user.mails.map((mail) => Mail.findById(mail))
|
|
||||||
);
|
|
||||||
|
|
||||||
let contact = {
|
|
||||||
mail: mails.filter((e) => !!e),
|
|
||||||
phone: ctx.user.phones,
|
|
||||||
};
|
|
||||||
|
|
||||||
return contact;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,265 +0,0 @@
|
|||||||
import { Server, LoginState, TFAOption, TFAType } from "@hibas123/openauth-internalapi";
|
|
||||||
import type { SessionContext } from "../index";
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import User, { IUser } from "../../../models/user";
|
|
||||||
import moment from "moment";
|
|
||||||
import crypto from "node:crypto";
|
|
||||||
import TwoFactor, { ITwoFactor, IWebAuthn } from "../../../models/twofactor";
|
|
||||||
import speakeasy from "speakeasy";
|
|
||||||
import { generateAuthenticationOptions, verifyAuthenticationResponse } from "@simplewebauthn/server";
|
|
||||||
import config from "../../../config";
|
|
||||||
|
|
||||||
//FIXME: There are a lot of uneccessary database requests happening here. Since this is not a "hot" path, it should not matter to much, but it should be fixed nontheless.
|
|
||||||
|
|
||||||
export default class LoginService extends Server.LoginService<SessionContext> {
|
|
||||||
private async getUser(username: string): Promise<IUser> {
|
|
||||||
let user = await User.findOne({ username: username.toLowerCase() });
|
|
||||||
if (!user) {
|
|
||||||
throw new Error("User not found");
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getLoginState(ctx: SessionContext): Promise<LoginState> {
|
|
||||||
if (ctx.user && ctx.session.validated) {
|
|
||||||
return {
|
|
||||||
success: true
|
|
||||||
}
|
|
||||||
} else if (ctx.session.login_state) {
|
|
||||||
//TODO: Check login_state expiration or so
|
|
||||||
if (ctx.session.login_state.username) {
|
|
||||||
let user = await this.getUser(ctx.session.login_state.username);
|
|
||||||
if (!ctx.session.login_state.password_correct) {
|
|
||||||
let passwordSalt = user.salt;
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
username: ctx.session.login_state.username,
|
|
||||||
password: false,
|
|
||||||
passwordSalt: passwordSalt,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let tfa = await this.getTwoFactors(await this.getUser(ctx.session.login_state.username))
|
|
||||||
if (tfa.length <= 0) {
|
|
||||||
ctx.session.user_id = user._id.toString();
|
|
||||||
ctx.session.login_state = undefined;
|
|
||||||
Logging.warn("This should have been set somewhere else!");
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
username: ctx.session.login_state.username,
|
|
||||||
password: true,
|
|
||||||
requireTwoFactor: tfa,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
username: undefined,
|
|
||||||
password: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getTwoFactors(user: IUser): Promise<TFAOption[]> {
|
|
||||||
let twofactors = await TwoFactor.find({
|
|
||||||
user: user._id,
|
|
||||||
valid: true
|
|
||||||
})
|
|
||||||
|
|
||||||
return twofactors.map<TFAOption>(tf => {
|
|
||||||
return {
|
|
||||||
id: tf._id.toString(),
|
|
||||||
name: tf.name,
|
|
||||||
tfatype: tf.type as number,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async enableSession(ctx: SessionContext) {
|
|
||||||
let user = await this.getUser(ctx.session.login_state.username);
|
|
||||||
ctx.user = user;
|
|
||||||
ctx.session.user_id = user._id.toString();
|
|
||||||
ctx.session.login_state = undefined;
|
|
||||||
ctx.session.validated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
GetState(ctx: SessionContext): Promise<LoginState> {
|
|
||||||
return this.getLoginState(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Start(username: string, ctx: SessionContext): Promise<LoginState> {
|
|
||||||
let user = await this.getUser(username);
|
|
||||||
|
|
||||||
ctx.session.login_state = {
|
|
||||||
username: username,
|
|
||||||
password_correct: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getLoginState(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
async UsePassword(password_hash: string, date: number, ctx: SessionContext): Promise<LoginState> {
|
|
||||||
if (!ctx.session.login_state) {
|
|
||||||
throw new Error("No login state. Call Start() first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await this.getUser(ctx.session.login_state.username);
|
|
||||||
|
|
||||||
if (date <= 0) {
|
|
||||||
if (user.password !== password_hash) {
|
|
||||||
throw new Error("Password incorrect");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
!moment(date).isBetween(
|
|
||||||
moment().subtract(1, "minute"),
|
|
||||||
moment().add(1, "minute")
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new Error("Date incorrect. Please check your devices time!");
|
|
||||||
} else {
|
|
||||||
let upw = crypto
|
|
||||||
.createHash("sha512")
|
|
||||||
.update(user.password + date.toString())
|
|
||||||
.digest("hex");
|
|
||||||
|
|
||||||
if (upw !== password_hash) {
|
|
||||||
throw new Error("Password incorrect");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.session.login_state.password_correct = true;
|
|
||||||
|
|
||||||
let tfas = await this.getTwoFactors(user);
|
|
||||||
if (tfas.length <= 0) {
|
|
||||||
await this.enableSession(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.getLoginState(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private async getAndCheckTFA<T extends ITwoFactor>(id: string, shouldType: TFAType, ctx: SessionContext): Promise<T> {
|
|
||||||
if (!ctx.session.login_state) {
|
|
||||||
throw new Error("No login state. Call Start() first.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await this.getUser(ctx.session.login_state.username);
|
|
||||||
|
|
||||||
let tfa = await TwoFactor.findById(id);
|
|
||||||
if (!tfa || tfa.user.toString() != user._id.toString()) {
|
|
||||||
throw new Error("Two factor not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tfa.type != shouldType as number) {
|
|
||||||
throw new Error("Two factor is not the correct type!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tfa.valid) {
|
|
||||||
throw new Error("Two factor is not valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tfa.expires && moment().isAfter(moment(tfa.expires))) {
|
|
||||||
throw new Error("Two factor is expired");
|
|
||||||
}
|
|
||||||
|
|
||||||
return tfa as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
async UseTOTP(id: string, code: string, ctx: SessionContext): Promise<LoginState> {
|
|
||||||
let tfa = await this.getAndCheckTFA(id, TFAType.TOTP, ctx);
|
|
||||||
|
|
||||||
let valid = speakeasy.totp.verify({
|
|
||||||
secret: tfa.data,
|
|
||||||
encoding: "base32",
|
|
||||||
token: code,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
throw new Error("Code incorrect");
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.enableSession(ctx);
|
|
||||||
|
|
||||||
return this.getLoginState(ctx);
|
|
||||||
}
|
|
||||||
async UseBackupCode(id: string, code: string, ctx: SessionContext): Promise<LoginState> {
|
|
||||||
let tfa = await this.getAndCheckTFA(id, TFAType.BACKUP_CODE, ctx);
|
|
||||||
|
|
||||||
if (tfa.data.indexOf(code) < 0) {
|
|
||||||
throw new Error("Code incorrect");
|
|
||||||
}
|
|
||||||
|
|
||||||
tfa.data = tfa.data.filter(c => c != code);
|
|
||||||
|
|
||||||
await TwoFactor.save(tfa);
|
|
||||||
//TODO: handle the case where the last backup code is used
|
|
||||||
|
|
||||||
await this.enableSession(ctx);
|
|
||||||
|
|
||||||
return this.getLoginState(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
async GetWebAuthnChallenge(id: string, ctx: SessionContext): Promise<string> {
|
|
||||||
let tfa = await this.getAndCheckTFA<IWebAuthn>(id, TFAType.WEBAUTHN, ctx);
|
|
||||||
|
|
||||||
const rpID = new URL(config.core.url).hostname;
|
|
||||||
|
|
||||||
let options = generateAuthenticationOptions({
|
|
||||||
timeout: 60000,
|
|
||||||
userVerification: "discouraged",
|
|
||||||
rpID,
|
|
||||||
allowCredentials: [{
|
|
||||||
id: tfa.data.device.credentialID.buffer,
|
|
||||||
type: "public-key",
|
|
||||||
transports: tfa.data.device.transports
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.session.login_state.webauthn_challenge = options.challenge;
|
|
||||||
|
|
||||||
Logging.debug("Challenge", options, tfa, tfa.data.device.credentialID);
|
|
||||||
|
|
||||||
return JSON.stringify(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async UseWebAuthn(id: string, response: string, ctx: SessionContext): Promise<LoginState> {
|
|
||||||
let tfa = await this.getAndCheckTFA<IWebAuthn>(id, TFAType.WEBAUTHN, ctx);
|
|
||||||
|
|
||||||
if (!ctx.session.login_state.webauthn_challenge) {
|
|
||||||
throw new Error("No webauthn challenge");
|
|
||||||
}
|
|
||||||
|
|
||||||
let rpID = new URL(config.core.url).hostname;
|
|
||||||
|
|
||||||
let verification = await verifyAuthenticationResponse({
|
|
||||||
response: JSON.parse(response),
|
|
||||||
authenticator: {
|
|
||||||
counter: tfa.data.device.counter,
|
|
||||||
credentialID: tfa.data.device.credentialID.buffer,
|
|
||||||
credentialPublicKey: tfa.data.device.credentialPublicKey.buffer,
|
|
||||||
transports: tfa.data.device.transports
|
|
||||||
},
|
|
||||||
expectedChallenge: ctx.session.login_state.webauthn_challenge,
|
|
||||||
expectedOrigin: config.core.url,
|
|
||||||
expectedRPID: rpID,
|
|
||||||
requireUserVerification: false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (verification.verified) {
|
|
||||||
tfa.data.device.counter = verification.authenticationInfo.newCounter;
|
|
||||||
await TwoFactor.save(tfa);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.enableSession(ctx);
|
|
||||||
|
|
||||||
return this.getLoginState(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import { Server, Session } from "@hibas123/openauth-internalapi";
|
|
||||||
import type { SessionContext } from "../index";
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import { RequireLogin } from "../../../helper/login";
|
|
||||||
import crypto from "node:crypto";
|
|
||||||
import User from "../../../models/user";
|
|
||||||
|
|
||||||
export default class SecurityService extends Server.SecurityService<SessionContext> {
|
|
||||||
@RequireLogin()
|
|
||||||
async GetSessions(ctx: SessionContext): Promise<Session[]> {
|
|
||||||
return []
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
@RequireLogin()
|
|
||||||
async RevokeSession(id: string, ctx: SessionContext): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async ChangePassword(old_pw: string, new_pw: string, ctx: SessionContext): Promise<void> {
|
|
||||||
let old_pw_hash = crypto.createHash("sha512").update(ctx.user.salt + old_pw).digest("hex");
|
|
||||||
|
|
||||||
if (old_pw_hash != ctx.user.password) {
|
|
||||||
throw new Error("Wrong password");
|
|
||||||
}
|
|
||||||
|
|
||||||
let salt = crypto.randomBytes(32).toString("base64");
|
|
||||||
let password_hash = crypto.createHash("sha512").update(salt + new_pw).digest("hex");
|
|
||||||
|
|
||||||
ctx.user.salt = salt;
|
|
||||||
ctx.user.password = password_hash;
|
|
||||||
|
|
||||||
await User.save(ctx.user);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
import { TFANewTOTP, Server, TFAOption, UserRegisterInfo, TFAWebAuthRegister } from "@hibas123/openauth-internalapi";
|
|
||||||
import type { SessionContext } from "../index";
|
|
||||||
import TwoFactorModel, { ITOTP, IWebAuthn, TFATypes } from "../../../models/twofactor";
|
|
||||||
import moment = require("moment");
|
|
||||||
import * as speakeasy from "speakeasy";
|
|
||||||
import * as qrcode from "qrcode";
|
|
||||||
import config from "../../../config";
|
|
||||||
import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server';
|
|
||||||
import type { RegistrationResponseJSON } from '@simplewebauthn/typescript-types';
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import { Binary } from "mongodb";
|
|
||||||
import { RequireLogin } from "../../../helper/login";
|
|
||||||
|
|
||||||
|
|
||||||
export default class TFAService extends Server.TFAService<SessionContext> {
|
|
||||||
@RequireLogin()
|
|
||||||
AddBackupCodes(name: string, ctx: SessionContext): Promise<string[]> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
RemoveBackupCodes(id: string, ctx: SessionContext): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async GetOptions(ctx: SessionContext): Promise<TFAOption[]> {
|
|
||||||
let twofactor = await TwoFactorModel.find({ user: ctx.user._id, valid: true });
|
|
||||||
let expired = twofactor.filter((e) =>
|
|
||||||
e.expires ? moment().isAfter(moment(e.expires)) : false
|
|
||||||
);
|
|
||||||
await Promise.all(
|
|
||||||
expired.map((e) => {
|
|
||||||
e.valid = false;
|
|
||||||
return TwoFactorModel.save(e);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
twofactor = twofactor.filter((e) => e.valid);
|
|
||||||
let tfa = twofactor.map<TFAOption>((e) => {
|
|
||||||
return {
|
|
||||||
id: e._id.toString(),
|
|
||||||
name: e.name,
|
|
||||||
tfatype: e.type as number,
|
|
||||||
expires: e.expires?.valueOf()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return tfa;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async Delete(id: string, ctx: SessionContext): Promise<void> {
|
|
||||||
let twofactor = await TwoFactorModel.findById(id);
|
|
||||||
if (!twofactor || !twofactor.user.equals(ctx.user._id))
|
|
||||||
throw new Error("Invalid ID");
|
|
||||||
|
|
||||||
twofactor.valid = false;
|
|
||||||
await TwoFactorModel.save(twofactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async AddTOTP(name: string, ctx: SessionContext): Promise<TFANewTOTP> {
|
|
||||||
//Generating new
|
|
||||||
let secret = speakeasy.generateSecret({
|
|
||||||
name: config.core.name,
|
|
||||||
issuer: config.core.name,
|
|
||||||
otpauth_url: true
|
|
||||||
});
|
|
||||||
|
|
||||||
let twofactor = TwoFactorModel.new(<ITOTP>{
|
|
||||||
name: name,
|
|
||||||
user: ctx.user._id,
|
|
||||||
type: TFATypes.TOTP,
|
|
||||||
valid: false,
|
|
||||||
data: secret.base32,
|
|
||||||
});
|
|
||||||
|
|
||||||
let dataurl = await qrcode.toDataURL(secret.otpauth_url);
|
|
||||||
await TwoFactorModel.save(twofactor);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: twofactor._id.toString(),
|
|
||||||
qr: dataurl,
|
|
||||||
secret: secret.base32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async VerifyTOTP(id: string, code: string, ctx: SessionContext): Promise<void> {
|
|
||||||
let twofactor = await TwoFactorModel.findById(id);
|
|
||||||
if (!twofactor || !twofactor.user.equals(ctx.user._id))
|
|
||||||
throw new Error("Invalid ID");
|
|
||||||
|
|
||||||
let verified = speakeasy.totp.verify({
|
|
||||||
secret: twofactor.data,
|
|
||||||
encoding: "base32",
|
|
||||||
token: code,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!verified) throw new Error("Invalid code");
|
|
||||||
|
|
||||||
twofactor.valid = true;
|
|
||||||
twofactor.expires = undefined;
|
|
||||||
|
|
||||||
await TwoFactorModel.save(twofactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async AddWebauthn(name: string, ctx: SessionContext): Promise<TFAWebAuthRegister> {
|
|
||||||
// TODO: Get already registered options
|
|
||||||
|
|
||||||
const rpID = new URL(config.core.url).hostname;
|
|
||||||
const options = generateRegistrationOptions({
|
|
||||||
rpName: config.core.name,
|
|
||||||
rpID,
|
|
||||||
userID: ctx.user.uid,
|
|
||||||
userName: ctx.user.username,
|
|
||||||
attestationType: 'direct',
|
|
||||||
userDisplayName: ctx.user.name,
|
|
||||||
excludeCredentials: [],
|
|
||||||
authenticatorSelection: {
|
|
||||||
userVerification: "required",
|
|
||||||
requireResidentKey: false,
|
|
||||||
residentKey: "discouraged",
|
|
||||||
authenticatorAttachment: "cross-platform"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const twofactor = TwoFactorModel.new({
|
|
||||||
name,
|
|
||||||
type: TFATypes.WEBAUTHN,
|
|
||||||
user: ctx.user._id,
|
|
||||||
valid: false,
|
|
||||||
data: {
|
|
||||||
challenge: options.challenge
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await TwoFactorModel.save(twofactor);
|
|
||||||
|
|
||||||
Logging.debug(twofactor);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: twofactor._id.toString(),
|
|
||||||
challenge: JSON.stringify(options)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequireLogin()
|
|
||||||
async VerifyWebAuthn(id: string, registration: string, ctx: SessionContext): Promise<void> {
|
|
||||||
let twofactor = await TwoFactorModel.findById(id) as IWebAuthn;
|
|
||||||
if (!twofactor || !twofactor.user.equals(ctx.user._id))
|
|
||||||
throw new Error("Invalid ID");
|
|
||||||
|
|
||||||
const rpID = new URL(config.core.url).hostname;
|
|
||||||
|
|
||||||
const response = JSON.parse(registration) as RegistrationResponseJSON;
|
|
||||||
|
|
||||||
let verification = await verifyRegistrationResponse({
|
|
||||||
response,
|
|
||||||
expectedChallenge: twofactor.data.challenge,
|
|
||||||
expectedOrigin: config.core.url,
|
|
||||||
expectedRPID: rpID,
|
|
||||||
requireUserVerification: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (verification.verified) {
|
|
||||||
const { credentialPublicKey, credentialID, counter } = verification.registrationInfo;
|
|
||||||
|
|
||||||
//TODO: Check if already registered!
|
|
||||||
// TwoFactorModel.find({
|
|
||||||
// data: {
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
twofactor.data = {
|
|
||||||
device: {
|
|
||||||
credentialPublicKey: new Binary(credentialPublicKey),
|
|
||||||
credentialID: new Binary(credentialID),
|
|
||||||
counter: verification.registrationInfo.counter,
|
|
||||||
transports: response.response.transports as any[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
twofactor.valid = true;
|
|
||||||
|
|
||||||
await TwoFactorModel.save(twofactor);
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid response");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
import Mail from "../../models/mail";
|
|
||||||
import { GetClientApiAuthMiddleware } from "../middlewares/client";
|
|
||||||
import Stacker from "../middlewares/stacker";
|
|
||||||
import { Request, Response } from "express";
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
|
|
||||||
export default Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
|
|
||||||
const mode = req.query.mode;
|
|
||||||
let mails = await Promise.all(
|
|
||||||
req.user.mails.map((id) => Mail.findById(id))
|
|
||||||
);
|
|
||||||
|
|
||||||
let mail = mails.find((e) => e.primary) || mails[0];
|
|
||||||
|
|
||||||
let base_response = {
|
|
||||||
user_id: req.user.uid,
|
|
||||||
id: req.user.uid,
|
|
||||||
ID: req.user.uid,
|
|
||||||
sub: req.user.uid,
|
|
||||||
email: mail.mail,
|
|
||||||
username: req.user.username,
|
|
||||||
displayName: req.user.name,
|
|
||||||
displayNameClaim: req.user.name,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == "nextcloud") {
|
|
||||||
Logging.debug("Profile in Nextcloud mode");
|
|
||||||
base_response["ocs"] = {
|
|
||||||
data: {
|
|
||||||
id: base_response.user_id,
|
|
||||||
email: base_response.email,
|
|
||||||
"display-name": base_response.displayName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json(base_response);
|
|
||||||
})
|
|
@ -1,122 +0,0 @@
|
|||||||
import { Request, Response } from "express";
|
|
||||||
import RequestError, { HttpStatusCode } from "../../helper/request_error";
|
|
||||||
import User from "../../models/user";
|
|
||||||
import Client from "../../models/client";
|
|
||||||
import {
|
|
||||||
getAccessTokenJWT,
|
|
||||||
getIDToken,
|
|
||||||
AccessTokenJWTExp,
|
|
||||||
} from "../../helper/jwt";
|
|
||||||
import Stacker from "../middlewares/stacker";
|
|
||||||
import { GetClientAuthMiddleware } from "../middlewares/client";
|
|
||||||
import ClientCode from "../../models/client_code";
|
|
||||||
import Mail from "../../models/mail";
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
import moment = require("moment");
|
|
||||||
// import { JWTExpDur } from "../../keys";
|
|
||||||
import RefreshToken from "../../models/refresh_token";
|
|
||||||
import { getEncryptionKey } from "../../helper/user_key";
|
|
||||||
import { refreshTokenValidTime } from "../../config";
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
/*
|
|
||||||
For example, the authorization server could employ refresh token
|
|
||||||
rotation in which a new refresh token is issued with every access
|
|
||||||
token refresh response. The previous refresh token is invalidated but retained by the authorization server. If a refresh token is
|
|
||||||
compromised and subsequently used by both the attacker and the
|
|
||||||
legitimate client, one of them will present an invalidated refresh
|
|
||||||
token, which will inform the authorization server of the breach.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const RefreshTokenRoute = Stacker(
|
|
||||||
GetClientAuthMiddleware(false, false, true),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
let grant_type = req.query.grant_type || req.body.grant_type;
|
|
||||||
if (!grant_type || grant_type === "authorization_code") {
|
|
||||||
let code = req.query.code || req.body.code;
|
|
||||||
let nonce = req.query.nonce || req.body.nonce;
|
|
||||||
|
|
||||||
let c = await ClientCode.findOne({ code: code });
|
|
||||||
if (!c || moment(c.validTill).isBefore()) {
|
|
||||||
throw new RequestError(
|
|
||||||
req.__("Invalid code"),
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = await Client.findById(c.client);
|
|
||||||
|
|
||||||
let user = await User.findById(c.user);
|
|
||||||
let mails = await Promise.all(user.mails.map((m) => Mail.findOne(m)));
|
|
||||||
|
|
||||||
let token = RefreshToken.new({
|
|
||||||
user: c.user,
|
|
||||||
client: c.client,
|
|
||||||
permissions: c.permissions,
|
|
||||||
token: randomBytes(16).toString("hex"),
|
|
||||||
valid: true,
|
|
||||||
validTill: moment().add(refreshTokenValidTime).toDate(),
|
|
||||||
});
|
|
||||||
await RefreshToken.save(token);
|
|
||||||
await ClientCode.delete(c);
|
|
||||||
|
|
||||||
let mail = mails.find((e) => e.primary);
|
|
||||||
if (!mail) mail = mails[0];
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
refresh_token: token.token,
|
|
||||||
token: token.token,
|
|
||||||
access_token: await getAccessTokenJWT({
|
|
||||||
client: client,
|
|
||||||
user: user,
|
|
||||||
permissions: c.permissions,
|
|
||||||
}),
|
|
||||||
token_type: "bearer",
|
|
||||||
expires_in: AccessTokenJWTExp.asSeconds(),
|
|
||||||
profile: {
|
|
||||||
uid: user.uid,
|
|
||||||
email: mail ? mail.mail : "",
|
|
||||||
name: user.name,
|
|
||||||
enc_key: getEncryptionKey(user, client),
|
|
||||||
},
|
|
||||||
id_token: getIDToken(user, client.client_id, nonce),
|
|
||||||
});
|
|
||||||
} else if (grant_type === "refresh_token") {
|
|
||||||
let refresh_token = req.query.refresh_token || req.body.refresh_token;
|
|
||||||
if (!refresh_token)
|
|
||||||
throw new RequestError(
|
|
||||||
req.__("refresh_token not set"),
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
|
|
||||||
let token = await RefreshToken.findOne({ token: refresh_token });
|
|
||||||
if (!token || !token.valid || moment(token.validTill).isBefore())
|
|
||||||
throw new RequestError(
|
|
||||||
req.__("Invalid token"),
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
|
|
||||||
token.validTill = moment().add(refreshTokenValidTime).toDate();
|
|
||||||
await RefreshToken.save(token);
|
|
||||||
|
|
||||||
let user = await User.findById(token.user);
|
|
||||||
let client = await Client.findById(token.client);
|
|
||||||
let jwt = await getAccessTokenJWT({
|
|
||||||
user,
|
|
||||||
client,
|
|
||||||
permissions: token.permissions,
|
|
||||||
});
|
|
||||||
res.json({
|
|
||||||
access_token: jwt,
|
|
||||||
expires_in: AccessTokenJWTExp.asSeconds(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new RequestError(
|
|
||||||
"invalid grant_type",
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default RefreshTokenRoute;
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
import Register from "./register";
|
|
||||||
import OAuthRoute from "./oauth";
|
|
||||||
|
|
||||||
const UserRoute: Router = Router();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @api {post} /user/register
|
|
||||||
* @apiName UserRegister
|
|
||||||
*
|
|
||||||
* @apiGroup user
|
|
||||||
* @apiPermission none
|
|
||||||
*
|
|
||||||
* @apiParam {String} mail EMail linked to this Account
|
|
||||||
* @apiParam {String} username The new Username
|
|
||||||
* @apiParam {String} password Password hashed and salted like specification
|
|
||||||
* @apiParam {String} salt The Salt used for password hashing
|
|
||||||
* @apiParam {String} regcode The regcode, that should be used
|
|
||||||
* @apiParam {String} gender Gender can be: "male", "female", "other", "none"
|
|
||||||
* @apiParam {String} name The real name of the User
|
|
||||||
*
|
|
||||||
* @apiSuccess {Boolean} success
|
|
||||||
*
|
|
||||||
* @apiErrorExample {Object} Error-Response:
|
|
||||||
{
|
|
||||||
error: [
|
|
||||||
{
|
|
||||||
message: "Some Error",
|
|
||||||
field: "username"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
status: 400
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
UserRoute.post("/register", Register);
|
|
||||||
|
|
||||||
UserRoute.use("/oauth", OAuthRoute);
|
|
||||||
|
|
||||||
export default UserRoute;
|
|
@ -1,21 +0,0 @@
|
|||||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
|
||||||
import Client, { IClient } from "../../../models/client";
|
|
||||||
|
|
||||||
export async function getClientWithOrigin(client_id: string, origin: string) {
|
|
||||||
const client = await Client.findOne({
|
|
||||||
client_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const clientNotFoundError = new RequestError(
|
|
||||||
"Client not found!",
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!client) throw clientNotFoundError;
|
|
||||||
|
|
||||||
const clientUrl = new URL(client.redirect_url);
|
|
||||||
|
|
||||||
if (clientUrl.hostname !== origin) throw clientNotFoundError;
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Router } from "express";
|
|
||||||
import { GetJWTByUser } from "./jwt";
|
|
||||||
import { GetPermissionsForAuthRequest } from "./permissions";
|
|
||||||
import { GetTokenByUser } from "./refresh_token";
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
router.get("/jwt", GetJWTByUser);
|
|
||||||
router.get("/permissions", GetPermissionsForAuthRequest);
|
|
||||||
router.get("/refresh_token", GetTokenByUser);
|
|
||||||
|
|
||||||
export default router;
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Request, Response } from "express";
|
|
||||||
import Stacker from "../../middlewares/stacker";
|
|
||||||
import { GetUserMiddleware } from "../../middlewares/user";
|
|
||||||
import { URL } from "url";
|
|
||||||
import Client from "../../../models/client";
|
|
||||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
|
||||||
import { getAccessTokenJWT } from "../../../helper/jwt";
|
|
||||||
import { getClientWithOrigin } from "./_helper";
|
|
||||||
|
|
||||||
export const GetJWTByUser = Stacker(
|
|
||||||
GetUserMiddleware(true, false),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
const { client_id, origin } = req.query as { [key: string]: string };
|
|
||||||
|
|
||||||
const client = await getClientWithOrigin(client_id, origin);
|
|
||||||
|
|
||||||
const jwt = await getAccessTokenJWT({
|
|
||||||
user: req.user,
|
|
||||||
client: client,
|
|
||||||
permissions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
res.json({ jwt });
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,38 +0,0 @@
|
|||||||
import { Request, Response } from "express";
|
|
||||||
import Stacker from "../../middlewares/stacker";
|
|
||||||
import { GetUserMiddleware } from "../../middlewares/user";
|
|
||||||
import { URL } from "url";
|
|
||||||
import Client from "../../../models/client";
|
|
||||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
import moment = require("moment");
|
|
||||||
import RefreshToken from "../../../models/refresh_token";
|
|
||||||
import { refreshTokenValidTime } from "../../../config";
|
|
||||||
import { getClientWithOrigin } from "./_helper";
|
|
||||||
import Permission from "../../../models/permissions";
|
|
||||||
|
|
||||||
export const GetPermissionsForAuthRequest = Stacker(
|
|
||||||
GetUserMiddleware(true, false),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
const { client_id, origin, permissions } = req.query as {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const client = await getClientWithOrigin(client_id, origin);
|
|
||||||
|
|
||||||
const perm = permissions.split(",").filter((e) => !!e);
|
|
||||||
|
|
||||||
const resolved = await Promise.all(
|
|
||||||
perm.map((p) => Permission.findById(p))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resolved.some((e) => e.grant_type !== "user")) {
|
|
||||||
throw new RequestError(
|
|
||||||
"Invalid Permission requested",
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ permissions: resolved });
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,49 +0,0 @@
|
|||||||
import { Request, Response } from "express";
|
|
||||||
import Stacker from "../../middlewares/stacker";
|
|
||||||
import { GetUserMiddleware } from "../../middlewares/user";
|
|
||||||
import { URL } from "url";
|
|
||||||
import Client from "../../../models/client";
|
|
||||||
import RequestError, { HttpStatusCode } from "../../../helper/request_error";
|
|
||||||
import { randomBytes } from "crypto";
|
|
||||||
import moment = require("moment");
|
|
||||||
import RefreshToken from "../../../models/refresh_token";
|
|
||||||
import { refreshTokenValidTime } from "../../../config";
|
|
||||||
import { getClientWithOrigin } from "./_helper";
|
|
||||||
import Permission from "../../../models/permissions";
|
|
||||||
|
|
||||||
export const GetTokenByUser = Stacker(
|
|
||||||
GetUserMiddleware(true, false),
|
|
||||||
async (req: Request, res: Response) => {
|
|
||||||
const { client_id, origin, permissions } = req.query as {
|
|
||||||
[key: string]: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const client = await getClientWithOrigin(client_id, origin);
|
|
||||||
|
|
||||||
const perm = permissions.split(",").filter((e) => !!e);
|
|
||||||
|
|
||||||
const resolved = await Promise.all(
|
|
||||||
perm.map((p) => Permission.findById(p))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resolved.some((e) => e.grant_type !== "user")) {
|
|
||||||
throw new RequestError(
|
|
||||||
"Invalid Permission requested",
|
|
||||||
HttpStatusCode.BAD_REQUEST
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = RefreshToken.new({
|
|
||||||
user: req.user._id,
|
|
||||||
client: client._id,
|
|
||||||
permissions: resolved.map((e) => e._id),
|
|
||||||
token: randomBytes(16).toString("hex"),
|
|
||||||
valid: true,
|
|
||||||
validTill: moment().add(refreshTokenValidTime).toDate(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await RefreshToken.save(token);
|
|
||||||
|
|
||||||
res.json({ token });
|
|
||||||
}
|
|
||||||
);
|
|
@ -1,155 +0,0 @@
|
|||||||
import { Request, Response, Router } from "express";
|
|
||||||
import Stacker from "../middlewares/stacker";
|
|
||||||
import verify, { Types } from "../middlewares/verify";
|
|
||||||
import promiseMiddleware from "../../helper/promiseMiddleware";
|
|
||||||
import User, { Gender } from "../../models/user";
|
|
||||||
import { HttpStatusCode } from "../../helper/request_error";
|
|
||||||
import Mail from "../../models/mail";
|
|
||||||
import RegCode from "../../models/regcodes";
|
|
||||||
|
|
||||||
const Register = Stacker(
|
|
||||||
verify({
|
|
||||||
mail: {
|
|
||||||
type: Types.EMAIL,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
type: Types.STRING,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: Types.STRING,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
salt: {
|
|
||||||
type: Types.STRING,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
regcode: {
|
|
||||||
type: Types.STRING,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
gender: {
|
|
||||||
type: Types.STRING,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: Types.STRING,
|
|
||||||
notempty: true,
|
|
||||||
},
|
|
||||||
// birthday: {
|
|
||||||
// type: Types.DATE
|
|
||||||
// }
|
|
||||||
}),
|
|
||||||
promiseMiddleware(async (req: Request, res: Response) => {
|
|
||||||
let {
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
salt,
|
|
||||||
mail,
|
|
||||||
gender,
|
|
||||||
name,
|
|
||||||
birthday,
|
|
||||||
regcode,
|
|
||||||
} = req.body;
|
|
||||||
let u = await User.findOne({ username: username.toLowerCase() });
|
|
||||||
if (u) {
|
|
||||||
let err = {
|
|
||||||
message: [
|
|
||||||
{
|
|
||||||
message: req.__("Username taken"),
|
|
||||||
field: "username",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
nolog: true,
|
|
||||||
};
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
let m = await Mail.findOne({ mail: mail });
|
|
||||||
if (m) {
|
|
||||||
let err = {
|
|
||||||
message: [
|
|
||||||
{
|
|
||||||
message: req.__("Mail linked with other account"),
|
|
||||||
field: "mail",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
nolog: true,
|
|
||||||
};
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
let regc = await RegCode.findOne({ token: regcode });
|
|
||||||
if (!regc) {
|
|
||||||
let err = {
|
|
||||||
message: [
|
|
||||||
{
|
|
||||||
message: req.__("Invalid registration code"),
|
|
||||||
field: "regcode",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
nolog: true,
|
|
||||||
};
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!regc.valid) {
|
|
||||||
let err = {
|
|
||||||
message: [
|
|
||||||
{
|
|
||||||
message: req.__("Registration code already used"),
|
|
||||||
field: "regcode",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
status: HttpStatusCode.BAD_REQUEST,
|
|
||||||
nolog: true,
|
|
||||||
};
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
let g = -1;
|
|
||||||
switch (gender) {
|
|
||||||
case "male":
|
|
||||||
g = Gender.male;
|
|
||||||
break;
|
|
||||||
case "female":
|
|
||||||
g = Gender.female;
|
|
||||||
break;
|
|
||||||
case "other":
|
|
||||||
g = Gender.other;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
g = Gender.none;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = User.new({
|
|
||||||
username: username.toLowerCase(),
|
|
||||||
password: password,
|
|
||||||
salt: salt,
|
|
||||||
gender: g,
|
|
||||||
name: name,
|
|
||||||
// birthday: birthday,
|
|
||||||
admin: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
regc.valid = false;
|
|
||||||
await RegCode.save(regc);
|
|
||||||
|
|
||||||
let ml = Mail.new({
|
|
||||||
mail: mail,
|
|
||||||
primary: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
await Mail.save(ml);
|
|
||||||
|
|
||||||
user.mails.push(ml._id);
|
|
||||||
await User.save(user);
|
|
||||||
res.json({ success: true });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
export default Register;
|
|
@ -1,91 +0,0 @@
|
|||||||
import { parse } from "@hibas123/config";
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
import moment = require("moment");
|
|
||||||
|
|
||||||
export const refreshTokenValidTime = moment.duration(6, "month");
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
export interface DatabaseConfig {
|
|
||||||
host: string;
|
|
||||||
database: string;
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WebConfig {
|
|
||||||
port: string;
|
|
||||||
secure: "true" | "false" | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CoreConfig {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
dev: boolean;
|
|
||||||
secret: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Config {
|
|
||||||
core: CoreConfig;
|
|
||||||
database: DatabaseConfig;
|
|
||||||
web: WebConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = (parse(
|
|
||||||
{
|
|
||||||
core: {
|
|
||||||
dev: {
|
|
||||||
default: false,
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: "Open Auth",
|
|
||||||
},
|
|
||||||
url: String,
|
|
||||||
secret: {
|
|
||||||
type: String,
|
|
||||||
optional: false,
|
|
||||||
description: "Cookie secret"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
database: {
|
|
||||||
database: {
|
|
||||||
type: String,
|
|
||||||
default: "openauth",
|
|
||||||
},
|
|
||||||
host: {
|
|
||||||
type: String,
|
|
||||||
default: "localhost",
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
web: {
|
|
||||||
port: {
|
|
||||||
type: Number,
|
|
||||||
default: 3004,
|
|
||||||
},
|
|
||||||
secure: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"config.ini"
|
|
||||||
) as any) as Config;
|
|
||||||
|
|
||||||
if (process.env.DEV === "true") config.core.dev = true;
|
|
||||||
if (config.core.dev)
|
|
||||||
Logging.warning(
|
|
||||||
"DEV mode active. This can cause major performance issues, data loss and vulnerabilities! "
|
|
||||||
);
|
|
||||||
|
|
||||||
export default config;
|
|
@ -1,21 +0,0 @@
|
|||||||
import SafeMongo from "@hibas123/safe_mongo";
|
|
||||||
import Config from "./config";
|
|
||||||
|
|
||||||
|
|
||||||
const host = Config.database.host || "localhost";
|
|
||||||
// const port = Config.database.port || "27017";
|
|
||||||
const port = "27017";
|
|
||||||
const database = Config.database.database || "openauth";
|
|
||||||
const url = new URL(`mongodb://${host}:${port}/${database}`);
|
|
||||||
|
|
||||||
const user = Config.database.username || undefined;
|
|
||||||
const passwd = Config.database.password || undefined;
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
url.username = user;
|
|
||||||
if (passwd) url.password = passwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const DB = new SafeMongo(url.href, database);
|
|
||||||
export default DB;
|
|
23
Backend/src/express.d.ts
vendored
23
Backend/src/express.d.ts
vendored
@ -1,23 +0,0 @@
|
|||||||
import { IUser } from "./models/user";
|
|
||||||
import { IClient } from "./models/client";
|
|
||||||
|
|
||||||
declare module "express" {
|
|
||||||
interface Request {
|
|
||||||
user: IUser;
|
|
||||||
client: IClient;
|
|
||||||
isAdmin: boolean;
|
|
||||||
special: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module 'express-session' {
|
|
||||||
interface SessionData {
|
|
||||||
user_id: string;
|
|
||||||
validated: boolean;
|
|
||||||
login_state: {
|
|
||||||
username: string;
|
|
||||||
password_correct: boolean;
|
|
||||||
webauthn_challenge?: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
import { IUser, Gender } from "../models/user";
|
|
||||||
import { ObjectId } from "bson";
|
|
||||||
import { createJWT } from "../keys";
|
|
||||||
import { IClient } from "../models/client";
|
|
||||||
import config from "../config";
|
|
||||||
import moment = require("moment");
|
|
||||||
|
|
||||||
export interface OAuthJWT {
|
|
||||||
user: string;
|
|
||||||
username: string;
|
|
||||||
permissions: string[];
|
|
||||||
application: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const issuer = config.core.url;
|
|
||||||
|
|
||||||
export const IDTokenJWTExp = moment.duration(30, "m").asSeconds();
|
|
||||||
export function getIDToken(user: IUser, client_id: string, nonce: string) {
|
|
||||||
return createJWT(
|
|
||||||
{
|
|
||||||
user: user.uid,
|
|
||||||
name: user.name,
|
|
||||||
nickname: user.username,
|
|
||||||
username: user.username,
|
|
||||||
preferred_username: user.username,
|
|
||||||
gender: Gender[user.gender],
|
|
||||||
nonce,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expiresIn: IDTokenJWTExp,
|
|
||||||
issuer,
|
|
||||||
algorithm: "RS256",
|
|
||||||
subject: user.uid,
|
|
||||||
audience: client_id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AccessTokenJWTExp = moment.duration(6, "h");
|
|
||||||
export function getAccessTokenJWT(token: {
|
|
||||||
user: IUser;
|
|
||||||
permissions: ObjectId[];
|
|
||||||
client: IClient;
|
|
||||||
}) {
|
|
||||||
return createJWT(
|
|
||||||
<OAuthJWT>{
|
|
||||||
user: token.user.uid,
|
|
||||||
username: token.user.username,
|
|
||||||
permissions: token.permissions.map((p) => p.toHexString()),
|
|
||||||
application: token.client.client_id,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expiresIn: AccessTokenJWTExp.asSeconds(),
|
|
||||||
issuer,
|
|
||||||
algorithm: "RS256",
|
|
||||||
subject: token.user.uid,
|
|
||||||
audience: token.client.client_id,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import { SessionContext } from "../api/jrpc";
|
|
||||||
|
|
||||||
export function requireLoginState(ctx: SessionContext, validated: boolean = true, special: boolean = false): boolean {
|
|
||||||
if (!ctx.user) return false;
|
|
||||||
if (validated && !ctx.session.validated) return false;
|
|
||||||
|
|
||||||
if (special) {
|
|
||||||
//TODO: Implement something...
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RequireLogin(validated = true, special = false) {
|
|
||||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
|
||||||
let original = descriptor.value;
|
|
||||||
|
|
||||||
descriptor.value = function (...args: any[]) {
|
|
||||||
let ctx = args[args.length - 1] as SessionContext;
|
|
||||||
if (!ctx) throw new Error("Invalid request");
|
|
||||||
|
|
||||||
if (!requireLoginState(ctx, validated, special)) {
|
|
||||||
throw new Error("Not logged in");
|
|
||||||
}
|
|
||||||
|
|
||||||
return original.apply(this, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
|
||||||
|
|
||||||
export default (
|
|
||||||
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
|
|
||||||
) => (req: Request, res: Response, next: NextFunction) => {
|
|
||||||
Promise.resolve(fn(req, res, next)).catch(next);
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
// import * as crypto from "crypto-js"
|
|
||||||
import { IUser } from "../models/user";
|
|
||||||
import { IClient } from "../models/client";
|
|
||||||
import * as crypto from "crypto";
|
|
||||||
|
|
||||||
function sha512(text: string) {
|
|
||||||
let hash = crypto.createHash("sha512");
|
|
||||||
hash.update(text);
|
|
||||||
return hash.digest("base64");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEncryptionKey(user: IUser, client: IClient) {
|
|
||||||
return sha512(
|
|
||||||
sha512(user.encryption_key) +
|
|
||||||
sha512(client._id.toHexString()) +
|
|
||||||
sha512(client.client_id)
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import config from "./config";
|
|
||||||
|
|
||||||
// import NLS from "@hibas123/nodeloggingserver_client";
|
|
||||||
// if (config.logging) {
|
|
||||||
// let s = NLS(Logging, config.logging.server, config.logging.appid, config.logging.token);
|
|
||||||
// s.send(`[${new Date().toLocaleTimeString()}] Starting application`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!config.database) {
|
|
||||||
// Logging.error("No database config set. Terminating.")
|
|
||||||
// process.exit();
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!config.web) {
|
|
||||||
Logging.error("No web config set. Terminating.");
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
import * as i18n from "i18n";
|
|
||||||
i18n.configure({
|
|
||||||
locales: ["en", "de"],
|
|
||||||
directory: "./locales",
|
|
||||||
});
|
|
||||||
|
|
||||||
import Web from "./web";
|
|
||||||
import TestData from "./testdata";
|
|
||||||
import DB from "./database";
|
|
||||||
|
|
||||||
Logging.log("Connecting to Database");
|
|
||||||
if (config.core.dev) {
|
|
||||||
Logging.warning("Running in dev mode! Database will be cleared!");
|
|
||||||
}
|
|
||||||
DB.connect()
|
|
||||||
.then(async () => {
|
|
||||||
Logging.log("Database connected", config);
|
|
||||||
if (config.core.dev) await TestData();
|
|
||||||
let web = new Web(config.web);
|
|
||||||
web.listen();
|
|
||||||
|
|
||||||
let already = new Set();
|
|
||||||
function print(path, layer) {
|
|
||||||
if (layer.route) {
|
|
||||||
layer.route.stack.forEach(
|
|
||||||
print.bind(null, path.concat(split(layer.route.path)))
|
|
||||||
);
|
|
||||||
} else if (layer.name === "router" && layer.handle.stack) {
|
|
||||||
layer.handle.stack.forEach(
|
|
||||||
print.bind(null, path.concat(split(layer.regexp)))
|
|
||||||
);
|
|
||||||
} else if (layer.method) {
|
|
||||||
let me: string = layer.method.toUpperCase();
|
|
||||||
me += " ".repeat(6 - me.length);
|
|
||||||
let msg = `${me} /${path
|
|
||||||
.concat(split(layer.regexp))
|
|
||||||
.filter(Boolean)
|
|
||||||
.join("/")}`;
|
|
||||||
if (!already.has(msg)) {
|
|
||||||
already.add(msg);
|
|
||||||
Logging.log(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function split(thing) {
|
|
||||||
if (typeof thing === "string") {
|
|
||||||
return thing.split("/");
|
|
||||||
} else if (thing.fast_slash) {
|
|
||||||
return "";
|
|
||||||
} else {
|
|
||||||
var match = thing
|
|
||||||
.toString()
|
|
||||||
.replace("\\/?", "")
|
|
||||||
.replace("(?=\\/|$)", "$")
|
|
||||||
.match(
|
|
||||||
/^\/\^((?:\\[.*+?^${}()|[\]\\\/]|[^.*+?^${}()|[\]\\\/])*)\$\//
|
|
||||||
);
|
|
||||||
return match
|
|
||||||
? match[1].replace(/\\(.)/g, "$1").split("/")
|
|
||||||
: "<complex:" + thing.toString() + ">";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Logging.log("--- Endpoints: ---");
|
|
||||||
// web.server._router.stack.forEach(print.bind(null, []))
|
|
||||||
// Logging.log("--- Endpoints end ---")
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
Logging.error(e);
|
|
||||||
process.exit();
|
|
||||||
});
|
|
@ -1,29 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
|
|
||||||
export interface IClientCode extends ModelDataBase {
|
|
||||||
user: ObjectId;
|
|
||||||
code: string;
|
|
||||||
client: ObjectId;
|
|
||||||
permissions: ObjectId[];
|
|
||||||
validTill: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ClientCode = DB.addModel<IClientCode>({
|
|
||||||
name: "client_code",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => { },
|
|
||||||
schema: {
|
|
||||||
user: { type: ObjectId },
|
|
||||||
code: { type: String },
|
|
||||||
client: { type: ObjectId },
|
|
||||||
permissions: { type: Array },
|
|
||||||
validTill: { type: Date },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
export default ClientCode;
|
|
@ -1,25 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
|
|
||||||
export interface IGrant extends ModelDataBase {
|
|
||||||
user: ObjectId;
|
|
||||||
client: ObjectId;
|
|
||||||
permissions: ObjectId[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Grant = DB.addModel<IGrant>({
|
|
||||||
name: "grant",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => { },
|
|
||||||
schema: {
|
|
||||||
user: { type: ObjectId },
|
|
||||||
client: { type: ObjectId },
|
|
||||||
permissions: { type: ObjectId, array: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Grant;
|
|
@ -1,76 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
import moment = require("moment");
|
|
||||||
|
|
||||||
export interface ILoginToken extends ModelDataBase {
|
|
||||||
token: string;
|
|
||||||
special: boolean;
|
|
||||||
user: ObjectId;
|
|
||||||
validTill: Date;
|
|
||||||
valid: boolean;
|
|
||||||
validated: boolean;
|
|
||||||
data: any;
|
|
||||||
ip: string;
|
|
||||||
browser: string;
|
|
||||||
}
|
|
||||||
const LoginToken = DB.addModel<ILoginToken>({
|
|
||||||
name: "login_token",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => { },
|
|
||||||
schema: {
|
|
||||||
token: { type: String },
|
|
||||||
special: { type: Boolean, default: () => false },
|
|
||||||
user: { type: ObjectId },
|
|
||||||
validTill: { type: Date },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
migration: (doc: ILoginToken) => {
|
|
||||||
doc.validated = true;
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
token: { type: String },
|
|
||||||
special: { type: Boolean, default: () => false },
|
|
||||||
user: { type: ObjectId },
|
|
||||||
validTill: { type: Date },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
validated: { type: Boolean, default: false },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
migration: (doc: ILoginToken) => {
|
|
||||||
doc.validated = true;
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
token: { type: String },
|
|
||||||
special: { type: Boolean, default: () => false },
|
|
||||||
user: { type: ObjectId },
|
|
||||||
validTill: { type: Date },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
validated: { type: Boolean, default: false },
|
|
||||||
data: { type: "any", optional: true },
|
|
||||||
ip: { type: String, optional: true },
|
|
||||||
browser: { type: String, optional: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function CheckToken(
|
|
||||||
token: ILoginToken,
|
|
||||||
validated: boolean = true
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (!token || !token.valid) return false;
|
|
||||||
if (validated && !token.validated) return false;
|
|
||||||
if (moment().isAfter(token.validTill)) {
|
|
||||||
token.valid = false;
|
|
||||||
await LoginToken.save(token);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoginToken;
|
|
@ -1,24 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo";
|
|
||||||
|
|
||||||
export interface IMail extends ModelDataBase {
|
|
||||||
mail: string;
|
|
||||||
verified: boolean;
|
|
||||||
primary: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Mail = DB.addModel<IMail>({
|
|
||||||
name: "mail",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => {},
|
|
||||||
schema: {
|
|
||||||
mail: { type: String },
|
|
||||||
verified: { type: Boolean, default: false },
|
|
||||||
primary: { type: Boolean },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Mail;
|
|
@ -1,37 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
|
|
||||||
export interface IPermission extends ModelDataBase {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
client: ObjectId;
|
|
||||||
grant_type: "user" | "client";
|
|
||||||
}
|
|
||||||
|
|
||||||
const Permission = DB.addModel<IPermission>({
|
|
||||||
name: "permission",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => { },
|
|
||||||
schema: {
|
|
||||||
name: { type: String },
|
|
||||||
description: { type: String },
|
|
||||||
client: { type: ObjectId },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
migration: (old) => {
|
|
||||||
old.grant_type = "user";
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
name: { type: String },
|
|
||||||
description: { type: String },
|
|
||||||
client: { type: ObjectId },
|
|
||||||
grant_type: { type: String, default: "user" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Permission;
|
|
@ -1,32 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
|
|
||||||
export interface IRefreshToken extends ModelDataBase {
|
|
||||||
token: string;
|
|
||||||
user: ObjectId;
|
|
||||||
client: ObjectId;
|
|
||||||
permissions: ObjectId[];
|
|
||||||
validTill: Date;
|
|
||||||
valid: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RefreshToken = DB.addModel<IRefreshToken>({
|
|
||||||
name: "refresh_token",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => { },
|
|
||||||
schema: {
|
|
||||||
token: { type: String },
|
|
||||||
user: { type: ObjectId },
|
|
||||||
client: { type: ObjectId },
|
|
||||||
permissions: { type: Array },
|
|
||||||
validTill: { type: Date },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default RefreshToken;
|
|
@ -1,71 +0,0 @@
|
|||||||
import { TFAType } from "@hibas123/openauth-internalapi";
|
|
||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "bson";
|
|
||||||
import { Binary } from "mongodb";
|
|
||||||
|
|
||||||
export { TFAType as TFATypes };
|
|
||||||
|
|
||||||
|
|
||||||
export const TFANames = new Map<TFAType, string>();
|
|
||||||
TFANames.set(TFAType.TOTP, "Authenticator");
|
|
||||||
TFANames.set(TFAType.BACKUP_CODE, "Backup Codes");
|
|
||||||
TFANames.set(TFAType.WEBAUTHN, "Security Key (WebAuthn)");
|
|
||||||
TFANames.set(TFAType.APP_ALLOW, "App Push");
|
|
||||||
|
|
||||||
export interface ITwoFactor extends ModelDataBase {
|
|
||||||
user: ObjectId;
|
|
||||||
valid: boolean;
|
|
||||||
expires?: Date;
|
|
||||||
name?: string;
|
|
||||||
type: TFAType;
|
|
||||||
data: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITOTP extends ITwoFactor {
|
|
||||||
data: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IWebAuthn extends ITwoFactor {
|
|
||||||
data: {
|
|
||||||
challenge?: any;
|
|
||||||
device?: {
|
|
||||||
credentialID: Binary;
|
|
||||||
credentialPublicKey: Binary;
|
|
||||||
counter: number;
|
|
||||||
transports: AuthenticatorTransport[]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IU2F extends ITwoFactor {
|
|
||||||
data: {
|
|
||||||
challenge?: string;
|
|
||||||
publicKey: string;
|
|
||||||
keyHandle: string;
|
|
||||||
registration?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IBackupCode extends ITwoFactor {
|
|
||||||
data: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const TwoFactor = DB.addModel<ITwoFactor>({
|
|
||||||
name: "twofactor",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: (e) => { },
|
|
||||||
schema: {
|
|
||||||
user: { type: ObjectId },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
expires: { type: Date, optional: true },
|
|
||||||
name: { type: String, optional: true },
|
|
||||||
type: { type: Number },
|
|
||||||
data: { type: "any" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default TwoFactor;
|
|
@ -1,134 +0,0 @@
|
|||||||
import DB from "../database";
|
|
||||||
import { ModelDataBase } from "@hibas123/safe_mongo/lib/model";
|
|
||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { randomString } from "../helper/random";
|
|
||||||
|
|
||||||
export enum Gender {
|
|
||||||
none,
|
|
||||||
male,
|
|
||||||
female,
|
|
||||||
other,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUser extends ModelDataBase {
|
|
||||||
uid: string;
|
|
||||||
username: string;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
birthday?: Date;
|
|
||||||
gender: Gender;
|
|
||||||
admin: boolean;
|
|
||||||
password: string;
|
|
||||||
salt: string;
|
|
||||||
mails: ObjectId[];
|
|
||||||
phones: { phone: string; verified: boolean; primary: boolean }[];
|
|
||||||
encryption_key: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const User = DB.addModel<IUser>({
|
|
||||||
name: "user",
|
|
||||||
versions: [
|
|
||||||
{
|
|
||||||
migration: () => { },
|
|
||||||
schema: {
|
|
||||||
uid: { type: String, default: () => v4() },
|
|
||||||
username: { type: String },
|
|
||||||
name: { type: String },
|
|
||||||
birthday: { type: Date, optional: true },
|
|
||||||
gender: { type: Number },
|
|
||||||
admin: { type: Boolean },
|
|
||||||
password: { type: String },
|
|
||||||
salt: { type: String },
|
|
||||||
mails: { type: Array, default: () => [] },
|
|
||||||
phones: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
phone: { type: String },
|
|
||||||
verified: { type: Boolean },
|
|
||||||
primary: { type: Boolean },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
twofactor: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
token: { type: String },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
type: { type: Number },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
migration: (e: IUser) => {
|
|
||||||
e.encryption_key = randomString(64);
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
uid: { type: String, default: () => v4() },
|
|
||||||
username: { type: String },
|
|
||||||
name: { type: String },
|
|
||||||
birthday: { type: Date, optional: true },
|
|
||||||
gender: { type: Number },
|
|
||||||
admin: { type: Boolean },
|
|
||||||
password: { type: String },
|
|
||||||
salt: { type: String },
|
|
||||||
mails: { type: Array, default: () => [] },
|
|
||||||
phones: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
phone: { type: String },
|
|
||||||
verified: { type: Boolean },
|
|
||||||
primary: { type: Boolean },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
twofactor: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
token: { type: String },
|
|
||||||
valid: { type: Boolean },
|
|
||||||
type: { type: Number },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
encryption_key: {
|
|
||||||
type: String,
|
|
||||||
default: () => randomString(64),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
migration: (e: any) => {
|
|
||||||
delete e.twofactor;
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
uid: { type: String, default: () => v4() },
|
|
||||||
username: { type: String },
|
|
||||||
name: { type: String },
|
|
||||||
birthday: { type: Date, optional: true },
|
|
||||||
gender: { type: Number },
|
|
||||||
admin: { type: Boolean },
|
|
||||||
password: { type: String },
|
|
||||||
salt: { type: String },
|
|
||||||
mails: { type: Array, default: () => [] },
|
|
||||||
phones: {
|
|
||||||
array: true,
|
|
||||||
model: true,
|
|
||||||
type: {
|
|
||||||
phone: { type: String },
|
|
||||||
verified: { type: Boolean },
|
|
||||||
primary: { type: Boolean },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
encryption_key: {
|
|
||||||
type: String,
|
|
||||||
default: () => randomString(64),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export default User;
|
|
@ -1,8 +0,0 @@
|
|||||||
import { __ as i__ } from "i18n";
|
|
||||||
import config from "../config";
|
|
||||||
import * as viewsv1 from "@hibas123/openauth-views-v1";
|
|
||||||
|
|
||||||
export default function GetAdminPage(__: typeof i__): string {
|
|
||||||
let data = {};
|
|
||||||
return viewsv1.admin(config.core.dev)(data, { helpers: { i18n: __ } });
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import { __ as i__ } from "i18n";
|
|
||||||
import config from "../config";
|
|
||||||
import * as viewsv1 from "@hibas123/openauth-views-v1";
|
|
||||||
|
|
||||||
export default function GetAuthPage(
|
|
||||||
__: typeof i__,
|
|
||||||
appname: string,
|
|
||||||
scopes: { name: string; description: string; logo: string }[]
|
|
||||||
): string {
|
|
||||||
|
|
||||||
return viewsv1.authorize(config.core.dev)(
|
|
||||||
{
|
|
||||||
title: __("Authorize %s", appname),
|
|
||||||
information: __(
|
|
||||||
"By clicking on ALLOW, you allow this app to access the requested recources."
|
|
||||||
),
|
|
||||||
scopes: scopes,
|
|
||||||
// request: request
|
|
||||||
},
|
|
||||||
{ helpers: { i18n: __ } }
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,121 +0,0 @@
|
|||||||
import {
|
|
||||||
IRouter,
|
|
||||||
Request,
|
|
||||||
RequestHandler,
|
|
||||||
Router,
|
|
||||||
static as ServeStatic,
|
|
||||||
} from "express";
|
|
||||||
import * as Handlebars from "handlebars";
|
|
||||||
import moment = require("moment");
|
|
||||||
import { GetUserMiddleware, UserMiddleware } from "../api/middlewares/user";
|
|
||||||
import GetAuthRoute from "../api/oauth/auth";
|
|
||||||
import config from "../config";
|
|
||||||
import { HttpStatusCode } from "../helper/request_error";
|
|
||||||
import GetAdminPage from "./admin";
|
|
||||||
import GetRegistrationPage from "./register";
|
|
||||||
import * as path from "path";
|
|
||||||
|
|
||||||
const viewsv2_location = path.join(path.dirname(require.resolve("@hibas123/openauth-views-v2")), "build");
|
|
||||||
|
|
||||||
|
|
||||||
Handlebars.registerHelper("appname", () => config.core.name);
|
|
||||||
|
|
||||||
const cacheTime = !config.core.dev
|
|
||||||
? moment.duration(1, "month").asSeconds()
|
|
||||||
: 1000;
|
|
||||||
|
|
||||||
const addCache: RequestHandler = (req, res, next) => {
|
|
||||||
res.setHeader("cache-control", "public, max-age=" + cacheTime);
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const ViewRouter: IRouter = Router();
|
|
||||||
ViewRouter.get("/", UserMiddleware, (req, res) => {
|
|
||||||
res.send("This is the main page");
|
|
||||||
});
|
|
||||||
|
|
||||||
ViewRouter.get("/register", (req, res) => {
|
|
||||||
res.setHeader("Cache-Control", "public, max-age=" + cacheTime);
|
|
||||||
res.send(GetRegistrationPage(req.__));
|
|
||||||
});
|
|
||||||
|
|
||||||
ViewRouter.use(
|
|
||||||
"/login",
|
|
||||||
addCache,
|
|
||||||
ServeStatic(path.join(viewsv2_location, "login"), { cacheControl: false })
|
|
||||||
);
|
|
||||||
|
|
||||||
ViewRouter.use(
|
|
||||||
"/user",
|
|
||||||
addCache,
|
|
||||||
ServeStatic(path.join(viewsv2_location, "user"), { cacheControl: false, })
|
|
||||||
);
|
|
||||||
|
|
||||||
ViewRouter.use(
|
|
||||||
"/static",
|
|
||||||
addCache,
|
|
||||||
ServeStatic(path.join(viewsv2_location, "../static"), { cacheControl: false, })
|
|
||||||
);
|
|
||||||
|
|
||||||
ViewRouter.get("/code", (req, res) => {
|
|
||||||
res.setHeader("Cache-Control", "no-cache");
|
|
||||||
if (req.query.error) res.send("Some error occured: " + req.query.error);
|
|
||||||
else res.send(`Your code is: ${req.query.code}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
ViewRouter.get(
|
|
||||||
"/admin",
|
|
||||||
GetUserMiddleware(false, true),
|
|
||||||
(req: Request, res, next) => {
|
|
||||||
if (!req.isAdmin) res.sendStatus(HttpStatusCode.FORBIDDEN);
|
|
||||||
else next();
|
|
||||||
},
|
|
||||||
(req, res) => {
|
|
||||||
res.send(GetAdminPage(req.__));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
ViewRouter.get("/auth", GetAuthRoute(true));
|
|
||||||
|
|
||||||
ViewRouter.use(
|
|
||||||
"/popup",
|
|
||||||
GetUserMiddleware(false, false),
|
|
||||||
addCache,
|
|
||||||
ServeStatic(path.join(viewsv2_location, "popup"), { cacheControl: false })
|
|
||||||
);
|
|
||||||
|
|
||||||
// ViewRouter.get("/popup", UserMiddleware, (req, res) => {
|
|
||||||
// res.send(GetPopupPage(req.__));
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (config.core.dev) {
|
|
||||||
// const logo =
|
|
||||||
// "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAlwSFlzAAASdAAAEnQB3mYfeAAAAAZiS0dEAP8A/wD/oL2nkwAAAFR0RVh0Y29tbWVudABGaWxlIHNvdXJjZTogaHR0cHM6Ly9jb21tb25zLndpa2ltZWRpYS5vcmcvd2lraS9GaWxlOkdvb2dsZS1mYXZpY29uLTIwMTUucG5nLE0iZQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0wOS0wMlQxNzo1ODowOCswMDowMNkAVU4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMDktMDJUMTc6NTg6MDgrMDA6MDCoXe3yAAAAR3RFWHRzb2Z0d2FyZQBJbWFnZU1hZ2ljayA2LjcuNy0xMCAyMDE0LTAzLTA2IFExNiBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ2+foqIAAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAYdEVYdFRodW1iOjpJbWFnZTo6aGVpZ2h0ADQwMUEAWp0AAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgAMzk0OUtQUwAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxNDQxMjE2Njg4mYj7RAAAABN0RVh0VGh1bWI6OlNpemUAMTcuN0tCQhK/wrgAAAAzdEVYdFRodW1iOjpVUkkAZmlsZTovLy90bXAvbG9jYWxjb3B5XzFmZGViZjk0YmZkZC0xLnBuZ8EhOmkAAAkUSURBVGhDzZkLcBXVGcf/u/f9yM07gDq06Rh5BZTAtKMVwQGqaFEZmNLWgoBO1RRFausjbR1qKR0rHUDFtwVtqWPHgc4UmBq1NASUiFQGwiskMqZMSW6Tm+Q+knvvvvp9u0uamNxXctO5P9i5u+ec3T3/c77zfd/ZCBqBMUANhaB2+Ok3CMgSYLVC9ORBLC2F6CswW2WPrAmJHa5D9EAtpGMNkC+2AvE4IIrGcRlV1Q/BRqLGXwn7zNlwzF0A54JbqVIw2oyQUQmRms4g8voLiB14H5qiQHS5AJsNsFghCIk7pr+S2kOSoMWi+r2OG+fBc99a2K+tMltlxoiESKdPIvirGvo9ATHPB9gdEAaOfIboXYjHoAaDsF59DXy/+A3s180ya9MjIyGaLKP70QdoBmohFhSRidDoJxn5jKGuaLSe1K4uMrn5KNj6GgSLxaxMTtpCYh/VoXv9/fRgK0AmlMx0Rg0LivbpA1e4fQfss683KxKTlj2EXtiMrgdWQHB76HCPrQiGni+46D30vsDq7yD00hazIjEphfTUPILIGy+S2xyX9jRnDV53Ah0SecAUJDWtnifXIVq7j9ZD4cjWApuIecroT0jzOdwt9T9+eH/8M3hX/dAsTUxCIcFtz6D3zVdgKSzOSITGsYJiiEZeCGTjPKoamwq/hutoVgXycrDbE86wIaIdeY9vgOfuNWZpcoYVEj10AN3V9xjmlO4IUqe1cAiC0wX79XNg/+Zc2KZUQiwuheBwQCPz0Do7IDWfI8dxEPFD/4AW7IFA7lugqH8ZXYSfRPx8IzzLV5qlqRkihIOT/+uTIOQXpBUb+HbukFhUDO9Dj8H17SVmTWqif38Pod9thNrepr+PUf1t8G34LdxLv6dfp8sQIYEHV0I+8U/da6SCRauBThLwE3gpKo+UMGUH4eeeJRUKfL/eCvedy8ya9BkkJHa0EV2rbqc8qCylSWlmelH89l9hLb/aLB05sSOHoHxxAe7lK8ySzBgkJL7/K+jZ5oMa9kJ0Ui6UQIu+HkhE6YdHIZKvzwX6F4EapE6JrSj6ZSMc0wNQQjb2nkNgr6R2B1C6rz5nRDD9QrTmGmh0pUWs8N3bBN/3m2kRkxiFpsUUxJOnBjpQsJkCJC3uXKLftKT95Ovt5Nc5klLPBZcCpdNJplYJNSqSC+X4EIO1YhKKfv9nviWn0GdE7dhrrAddBCNA66PNjy+Ooqc/hb0iSDHCSibVjfxNW802uYXec82/e4CRDUCmqBy1wFd9Bu7bz8M27RuwjL/CrMwtdNOS6ydSh/9FE0L7i2Eh6xNliJN3QJywyixLn4ZmGR80ynDY0ssS0oUzHq8TqF5IGzsWIr1H64Pznn7T+hK0jLSYDNuiPhJEd2bIm/VxbN4bo5dmWQiNb1zW8NmmPDIojeIF/TcWyfBo1EBwUj40AhGM1SLA5RDgzvLhoYPF6AajSQHjbckiOTs29yTzInfgLnPXuno1mhE5bBYngVXby4zzHIPFRKIsJM00nUK6eZJb0OqGQtMiwuI1RjwZrFVqN85zDN6DWmgyRMHGO0AuSaKGvJkWOWde5Bb68iVvaJiWvuNMLISbIU71csgsyQx+GX9YlBUtoyPZ2DJ6PXW/wGXGc8H7NSrUffDwUGMbie299I5ZkBkOirMFlCjnu4W0j5I8SpOop8nEsFk5rTTMov5NgCScuR9q66u0dx4a2S1QybIUPNNdiX3iPBy883mzZuyZWRNCoSexP5Jo1kq8Anav95gzUkZbyy9NCA+EFTJNhoalnbfg1eh0XAg0oqWn1Wgwxnx4ilIiHukEIhiZ+nztRONLjC5ELF5onPW7WA02QcJ5uRCV/mU4K+ejUKBM2OHDw3UbzTZjy866ONx28yIBMQm44RrjC4wuhBEn3EE6FCpQYaUE8Y3IVNzceRsJUuEWKEXhdSJacTrQgj81Udo/hjRdUnH8C0Vfl4ng9cN51vxKo1H/xkqLnIZQNw2gdGpl13zUx8ahWIwNmVpu3t7bgYPLdqHcd5VZml0WboogTvbPOVoiWMRXS0T8odr42tM/I4JnKlq812Fm22J8Gi9BsWWoCIa/rpS6irDgL6vRFukwS7PH2p19CMWSi2DCUeCem/7nnPqFMN3T96MtGiRTkvQYmQiLSJstmxc3vPtdfNJ+wiwdPeveiuJIs6JntclQKOXl9bOgMoGQWYUTsKR8Hnpl2nekwEpiCmnxL9v/MJ468pxZOjJawmdx6/ZjaDjnRL4ruQgmSN1bv2iwJ+hfIwOZ/MdFcFuc+singm8PS72wW2z40Yy7sXrKEjpP4W5MzgY+x5bjO/G31noUuDV4Ox6Eq2M5NAv1lJzMcHDEd1AQrH1y8KeoYYUc85/CXXurMc5doq+JdFBo3xmRe3VhVWXTMOeKWZhRMgnjXCVwWR2IqRK6oj16HOLnf9T2Gf4d9sNjc8PJwuk1mqUHtvBseC6yi6duiYP/LsLP9gc17HnEjYoJgwd5WCHMK43vYNPRl/WFnclfqFR6nESdjisS/crUHY36yGHVeI1VsOizZxNtlLVy1B74bGol9kFQ3PC0PgtrrJyuI7pIJhDWcO9cG9be4jAKBpBQCLOh4XnsPLMHJc7CjMQMZODj030GpYw0O2G429bBEVhM570IRVVUlYt4ec3wH9eTCmGe/mQ7Xj/1LsoynJnRolFgZlOzB2+C8vkTqBivYdfaxN8MUgphdpzejacattHMFOne6v8Fd61TuoQ5xXfgrdueMEuHJy0hzMmOJvzg/Z8iJsfhpQU61rPD66sz2oWHZqzAY7PuM0sTk7aQyzx+eDPeplwr354HB3mbbAtSKN/riYd1U35t/kZUFleYNcnJWAhzMdyGmo+34MDFBuTZPOQ+HRRzBsXWjOAu8AyE4hHk2d14tGoNVk6+y6xNjxEJuYy/txPbT+7CnpYPaBRDFERdlLFaaR1ZKWXgvcTQ2eLX8T+Ftgzc+ZgSpw2SpMeeNVOXYnH5zWbLzBiVkIGc6jyP2tbD+LjtOM51XdDNQ6POshgjjrAI45pn8CrveFSVTsGcK2fjWxNv1M10NGRNyHAEKJKzuXCA5FliJ5HvyNP3NdkF+C8dn/ikO2g+hwAAAABJRU5ErkJggg==";
|
|
||||||
// ViewRouter.get("/devauth", (req, res) => {
|
|
||||||
// res.send(
|
|
||||||
// GetAuthPage(req.__, "Test 05265", [
|
|
||||||
// {
|
|
||||||
// name: "Access Profile",
|
|
||||||
// description:
|
|
||||||
// "It allows the application to know who you are. Required for all applications. And a lot of more Text, because why not? This will not stop, till it is multiple lines long and maybe kill the layout, so keep reading as long as you like, but I promise it will get boring after some time. So this should be enougth.",
|
|
||||||
// logo: logo,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "Test 1",
|
|
||||||
// description:
|
|
||||||
// "This is not an real permission. This is used just to verify the layout",
|
|
||||||
// logo: logo,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// name: "Test 2",
|
|
||||||
// description:
|
|
||||||
// "This is not an real permission. This is used just to verify the layout",
|
|
||||||
// logo: logo,
|
|
||||||
// },
|
|
||||||
// ])
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default ViewRouter;
|
|
@ -1,9 +0,0 @@
|
|||||||
import { __ as i__ } from "i18n";
|
|
||||||
import config from "../config";
|
|
||||||
import * as viewsv1 from "@hibas123/openauth-views-v1";
|
|
||||||
|
|
||||||
export default function GetRegistrationPage(__: typeof i__): string {
|
|
||||||
let data = {};
|
|
||||||
return viewsv1.register(config.core.dev)(data, { helpers: { i18n: __ } });
|
|
||||||
}
|
|
||||||
|
|
@ -1,176 +0,0 @@
|
|||||||
import config, { WebConfig } from "./config";
|
|
||||||
import express from "express";
|
|
||||||
import { Express } from "express";
|
|
||||||
|
|
||||||
import Logging from "@hibas123/nodelogging";
|
|
||||||
import { Format } from "@hibas123/logging";
|
|
||||||
|
|
||||||
import bodyparser from "body-parser";
|
|
||||||
import cookieparser from "cookie-parser";
|
|
||||||
import session from "express-session";
|
|
||||||
import MongoStore from "connect-mongo";
|
|
||||||
|
|
||||||
import i18n from "i18n";
|
|
||||||
import compression from "compression";
|
|
||||||
import ApiRouter from "./api";
|
|
||||||
import ViewRouter from "./views";
|
|
||||||
import RequestError, { HttpStatusCode } from "./helper/request_error";
|
|
||||||
import DB from "./database";
|
|
||||||
import promiseMiddleware from "./helper/promiseMiddleware";
|
|
||||||
import User from "./models/user";
|
|
||||||
import LoginToken, { CheckToken } from "./models/login_token";
|
|
||||||
|
|
||||||
export default class Web {
|
|
||||||
server: Express;
|
|
||||||
private port: number;
|
|
||||||
|
|
||||||
constructor(config: WebConfig) {
|
|
||||||
this.server = express();
|
|
||||||
this.server.set("trust proxy", 1);
|
|
||||||
this.port = Number(config.port);
|
|
||||||
this.registerMiddleware();
|
|
||||||
this.registerUserSession();
|
|
||||||
this.registerEndpoints();
|
|
||||||
this.registerErrorHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
listen() {
|
|
||||||
this.server.listen(this.port, () => {
|
|
||||||
Logging.log(`Server listening on port ${this.port}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerMiddleware() {
|
|
||||||
this.server.use(session({
|
|
||||||
secret: config.core.secret,
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: false,
|
|
||||||
store: MongoStore.create({
|
|
||||||
client: DB.getClient() as any,
|
|
||||||
dbName: DB.db.databaseName,
|
|
||||||
collectionName: "sessions",
|
|
||||||
autoRemove: "native",
|
|
||||||
touchAfter: 60 * 60 * 24,
|
|
||||||
}),
|
|
||||||
cookie: {
|
|
||||||
maxAge: 1000 * 60 * 60 * 24 * 30 * 6,
|
|
||||||
secure: !config.core.dev,
|
|
||||||
sameSite: "strict",
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
this.server.use(cookieparser());
|
|
||||||
this.server.use(
|
|
||||||
bodyparser.json(),
|
|
||||||
bodyparser.urlencoded({ extended: true })
|
|
||||||
);
|
|
||||||
this.server.use(i18n.init);
|
|
||||||
|
|
||||||
//Logging Middleware
|
|
||||||
this.server.use((req, res, next) => {
|
|
||||||
let start = process.hrtime();
|
|
||||||
let finished = false;
|
|
||||||
let to = false;
|
|
||||||
let listener = () => {
|
|
||||||
if (finished) return;
|
|
||||||
finished = true;
|
|
||||||
let td = process.hrtime(start);
|
|
||||||
let time = !to ? (td[0] * 1e3 + td[1] / 1e6).toFixed(2) : "--.--";
|
|
||||||
let resFormat: (arg: any) => any = (arg) => arg;
|
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300)
|
|
||||||
resFormat = Format.green;
|
|
||||||
//Green
|
|
||||||
else if (res.statusCode === 304 || res.statusCode === 302)
|
|
||||||
resFormat = Format.yellow; //"\x1b[33m";
|
|
||||||
else if (res.statusCode >= 400 && res.statusCode < 500)
|
|
||||||
resFormat = Format.red; // "\x1b[36m";
|
|
||||||
//Cyan
|
|
||||||
else if (res.statusCode >= 500 && res.statusCode < 600)
|
|
||||||
resFormat = Format.cyan //"\x1b[31m"; //Red
|
|
||||||
|
|
||||||
let m = req.method;
|
|
||||||
while (m.length < 4) m += " ";
|
|
||||||
Logging.getChild("HTTP").log(
|
|
||||||
`${m} ${req.originalUrl} ${(req as any).language || ""
|
|
||||||
}`, resFormat(res.statusCode), `- ${time}ms`
|
|
||||||
);
|
|
||||||
res.removeListener("finish", listener);
|
|
||||||
};
|
|
||||||
res.on("finish", listener);
|
|
||||||
setTimeout(() => {
|
|
||||||
to = true;
|
|
||||||
listener();
|
|
||||||
}, 2000);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.server.use(
|
|
||||||
compression({
|
|
||||||
filter: (req, res) => {
|
|
||||||
if (req.headers["x-no-compression"]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return compression.filter(req, res);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerEndpoints() {
|
|
||||||
this.server.use("/api", ApiRouter);
|
|
||||||
this.server.use("/", ViewRouter);
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerErrorHandler() {
|
|
||||||
this.server.use((error, req: express.Request, res, next) => {
|
|
||||||
if (!(error instanceof RequestError)) {
|
|
||||||
error = new RequestError(
|
|
||||||
error.message,
|
|
||||||
error.status || HttpStatusCode.INTERNAL_SERVER_ERROR,
|
|
||||||
error.nolog || false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.status === 500 && !(<any>error).nolog) {
|
|
||||||
Logging.error(error);
|
|
||||||
} else {
|
|
||||||
Logging.log("Responded with Error", error.status, error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.accepts(["json"])) {
|
|
||||||
res.json_status = error.status || 500;
|
|
||||||
res.json({
|
|
||||||
error: error.message,
|
|
||||||
status: error.status || 500,
|
|
||||||
additional: error.additional,
|
|
||||||
});
|
|
||||||
} else res.status(error.status || 500).send(error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerUserSession() {
|
|
||||||
this.server.use(promiseMiddleware(async (req, res, next) => {
|
|
||||||
// if (!req.session.user_id) {
|
|
||||||
// if (req.cookies && req.cookies.login) {
|
|
||||||
// let token = await LoginToken.findOne({ token: req.cookies.login, valid: true });
|
|
||||||
// if (await CheckToken(token, true)) {
|
|
||||||
// req.session.user_id = token.user.toString();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (req.cookies && req.cookies.special) {
|
|
||||||
// let token = await LoginToken.findOne({ token: req.cookies.special, valid: true });
|
|
||||||
// if (await CheckToken(token, true)) {
|
|
||||||
// req.session.user_id = token.user.toString();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (req.session.user_id) {
|
|
||||||
req.user = await User.findById(req.session.user_id);
|
|
||||||
req.isAdmin = req.user.admin;
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "commonjs",
|
|
||||||
"declaration": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "./lib",
|
|
||||||
"strict": false,
|
|
||||||
"preserveWatchOutput": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"esModuleInterop": true
|
|
||||||
},
|
|
||||||
"exclude": ["node_modules/"],
|
|
||||||
"files": ["src/express.d.ts"],
|
|
||||||
"include": ["./src"]
|
|
||||||
}
|
|
38
Dockerfile
38
Dockerfile
@ -1,31 +1,33 @@
|
|||||||
FROM node:18-alpine
|
FROM node:12
|
||||||
|
|
||||||
LABEL maintainer="Fabian Stamm <dev@fabianstamm.de>"
|
LABEL maintainer="Fabian Stamm <dev@fabianstamm.de>"
|
||||||
|
|
||||||
|
# RUN apt-get update
|
||||||
|
|
||||||
|
# # for https
|
||||||
|
# RUN apt-get install -yyq ca-certificates
|
||||||
|
# # install libraries
|
||||||
|
# RUN apt-get install -yyq libappindicator1 libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6
|
||||||
|
# # tools
|
||||||
|
# RUN apt-get install -yyq gconf-service lsb-release wget xdg-utils
|
||||||
|
# # and fonts
|
||||||
|
# RUN apt-get install -yyq fonts-liberation
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app
|
RUN mkdir -p /usr/src/app
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
# COPY ["package.json", "yarn.lock", ".yarnrc.yml", "/usr/src/app/"]
|
COPY ["package.json", "package-lock.json", "tsconfig.json", "/usr/src/app/"]
|
||||||
# COPY .yarn /usr/src/app/.yarn
|
|
||||||
# COPY Backend /usr/src/app/Backend
|
|
||||||
# COPY Frontend /usr/src/app/Frontend
|
|
||||||
# COPY FrontendLegacy /usr/src/app/FrontendLegacy
|
|
||||||
|
|
||||||
COPY . /usr/src/app
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
# RUN rm -rf /usr/src/app/Backend/node_modules &&\
|
RUN npm install
|
||||||
# rm -rf /usr/src/app/Frontend/node_modules &&\
|
|
||||||
# rm -rf /usr/src/app/FrontendLegacy/node_modules &&\
|
|
||||||
# rm -rf /usr/src/app/Backend/logs &&\
|
|
||||||
# rm -rf /usr/src/app/Backend/keys
|
|
||||||
|
|
||||||
RUN yarn install
|
COPY lib/ /usr/src/app/lib
|
||||||
RUN yarn build
|
COPY views/out /usr/src/app/views/out/
|
||||||
|
COPY views_repo/build /usr/src/app/views_repo/build
|
||||||
RUN ln -s /usr/src/app/logs /usr/src/app/Backend/logs && ln -s /usr/src/app/keys /usr/src/app/Backend/keys
|
|
||||||
|
|
||||||
VOLUME [ "/usr/src/app/logs", "/usr/src/app/keys"]
|
VOLUME [ "/usr/src/app/logs", "/usr/src/app/keys"]
|
||||||
|
|
||||||
EXPOSE 3004/tcp
|
EXPOSE 3004/tcp
|
||||||
|
|
||||||
WORKDIR /usr/src/app/Backend
|
CMD ["npm", "run", "start"]
|
||||||
CMD ["npm", "run", "start"]
|
|
41
Earthfile
41
Earthfile
@ -1,41 +0,0 @@
|
|||||||
VERSION 0.7
|
|
||||||
FROM node:20-alpine3.18
|
|
||||||
WORKDIR /build
|
|
||||||
|
|
||||||
project:
|
|
||||||
COPY . .
|
|
||||||
RUN yarn install
|
|
||||||
|
|
||||||
build:
|
|
||||||
FROM +project
|
|
||||||
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
SAVE ARTIFACT /build/_API /API
|
|
||||||
SAVE ARTIFACT /build/Backend/lib /Backend
|
|
||||||
SAVE ARTIFACT /build/Frontend/build /Frontend
|
|
||||||
SAVE ARTIFACT /build/FrontendLegacy/out /FrontendLegacy
|
|
||||||
|
|
||||||
docker-multi:
|
|
||||||
BUILD +build
|
|
||||||
BUILD --platform linux/amd64 --platform linux/arm64 +docker
|
|
||||||
|
|
||||||
docker:
|
|
||||||
FROM +project
|
|
||||||
|
|
||||||
# RUN apk add --no-cache caddy supervisor
|
|
||||||
|
|
||||||
# COPY ./supervisord.conf /etc/supervisord.conf
|
|
||||||
|
|
||||||
COPY +build/API /build/_API
|
|
||||||
COPY +build/Backend /build/Backend/lib
|
|
||||||
COPY +build/Frontend /build/Frontend/build
|
|
||||||
COPY +build/FrontendLegacy /build/FrontendLegacy/out
|
|
||||||
|
|
||||||
WORKDIR /build/Backend
|
|
||||||
|
|
||||||
ENTRYPOINT ["node", "lib/index.js"]
|
|
||||||
|
|
||||||
ARG EARTHLY_TARGET_TAG
|
|
||||||
ARG TAG=$EARTHLY_TARGET_TAG
|
|
||||||
SAVE IMAGE --push git.hibas.dev/openserver/openauth:$TAG
|
|
@ -1,11 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 3
|
|
||||||
charset = utf-8
|
|
||||||
trim_trailing_whitespace = false
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.svelte]
|
|
||||||
indent_size = 2
|
|
8
Frontend/.gitignore
vendored
8
Frontend/.gitignore
vendored
@ -1,8 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
public/bundle.*
|
|
||||||
yarn.lock
|
|
||||||
.rpt2_cache
|
|
||||||
build/
|
|
||||||
build.js
|
|
||||||
*.old
|
|
@ -1,68 +0,0 @@
|
|||||||
_Psst <14>looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)_
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# svelte app
|
|
||||||
|
|
||||||
This is a project template for [Svelte](https://svelte.technology) apps. It lives at https://github.com/sveltejs/template.
|
|
||||||
|
|
||||||
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g degit # you only need to do this once
|
|
||||||
|
|
||||||
degit sveltejs/template svelte-app
|
|
||||||
cd svelte-app
|
|
||||||
```
|
|
||||||
|
|
||||||
_Note that you will need to have [Node.js](https://nodejs.org) installed._
|
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
Install the dependencies...
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd svelte-app
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
...then start [Rollup](https://rollupjs.org):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
|
||||||
|
|
||||||
## Deploying to the web
|
|
||||||
|
|
||||||
### With [now](https://zeit.co/now)
|
|
||||||
|
|
||||||
Install `now` if you haven't already:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g now
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, from within your project folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
now
|
|
||||||
```
|
|
||||||
|
|
||||||
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
|
|
||||||
|
|
||||||
### With [surge](https://surge.sh/)
|
|
||||||
|
|
||||||
Install `surge` if you haven't already:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g surge
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, from within your project folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
surge public
|
|
||||||
```
|
|
@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@hibas123/openauth-views-v2",
|
|
||||||
"main": "index.js",
|
|
||||||
"devDependencies": {
|
|
||||||
"@hibas123/openauth-internalapi": "workspace:^",
|
|
||||||
"@hibas123/theme": "^2.0.7",
|
|
||||||
"@hibas123/utils": "^2.2.18",
|
|
||||||
"@popperjs/core": "^2.11.8",
|
|
||||||
"@rollup/plugin-commonjs": "^24.0.1",
|
|
||||||
"@rollup/plugin-html": "^1.0.3",
|
|
||||||
"@rollup/plugin-image": "^3.0.3",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
|
||||||
"@simplewebauthn/browser": "^7.2.0",
|
|
||||||
"@tsconfig/svelte": "^4.0.1",
|
|
||||||
"@types/cleave.js": "^1.4.7",
|
|
||||||
"autoprefixer": "^10.4.14",
|
|
||||||
"classnames": "^2.3.2",
|
|
||||||
"cleave.js": "^1.6.0",
|
|
||||||
"cssnano": "^6.0.1",
|
|
||||||
"esbuild": "^0.17.16",
|
|
||||||
"flowbite": "^1.6.5",
|
|
||||||
"flowbite-svelte": "^0.34.9",
|
|
||||||
"joi": "^17.11.0",
|
|
||||||
"postcss": "^8.4.31",
|
|
||||||
"postcss-import": "^15.1.0",
|
|
||||||
"postcss-url": "^10.1.3",
|
|
||||||
"rollup": "^3.20.2",
|
|
||||||
"rollup-plugin-esbuild": "^5.0.0",
|
|
||||||
"rollup-plugin-hash": "^1.3.0",
|
|
||||||
"rollup-plugin-livereload": "^2.0.5",
|
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
|
||||||
"rollup-plugin-sizes": "^1.0.6",
|
|
||||||
"rollup-plugin-svelte": "^7.1.4",
|
|
||||||
"rollup-plugin-visualizer": "^5.9.0",
|
|
||||||
"svelte": "^3.58.0",
|
|
||||||
"svelte-preprocess": "^5.0.3",
|
|
||||||
"tailwindcss": "^3.3.1",
|
|
||||||
"typescript": "^5.0.4",
|
|
||||||
"what-the-pack": "^2.0.3"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"prepublishOnly": "npm run build",
|
|
||||||
"build": "rollup -c rollup.config.mjs ",
|
|
||||||
"dev": "rollup -c rollup.config.mjs -w"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
// cssnano: {},
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,121 +0,0 @@
|
|||||||
import svelte from "rollup-plugin-svelte";
|
|
||||||
import esbuild from "rollup-plugin-esbuild";
|
|
||||||
import html from "@rollup/plugin-html";
|
|
||||||
import resolve from "@rollup/plugin-node-resolve";
|
|
||||||
import image from "@rollup/plugin-image";
|
|
||||||
import sizes from "rollup-plugin-sizes";
|
|
||||||
import { visualizer } from "rollup-plugin-visualizer";
|
|
||||||
import postcss from "rollup-plugin-postcss";
|
|
||||||
import livereload from "rollup-plugin-livereload";
|
|
||||||
import sveltePreprocess from "svelte-preprocess";
|
|
||||||
import commonjs from "@rollup/plugin-commonjs";
|
|
||||||
import hash from "rollup-plugin-hash";
|
|
||||||
|
|
||||||
const VIEWS = ["home", "login", "popup", "user"];
|
|
||||||
|
|
||||||
const dev = process.env.NODE_ENV !== "production";
|
|
||||||
|
|
||||||
const htmlTemplate = ({ attributes, meta, files, publicPath, title }) => {
|
|
||||||
const makeHtmlAttributes = (attributes) => {
|
|
||||||
if (!attributes) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = Object.keys(attributes);
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
return keys.reduce(
|
|
||||||
(result, key) => (result += ` ${key}="${attributes[key]}"`),
|
|
||||||
""
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let bundle_name = "";
|
|
||||||
const scripts = (files.js || [])
|
|
||||||
.map(({ fileName }) => {
|
|
||||||
const attrs = makeHtmlAttributes(attributes.script);
|
|
||||||
if (fileName.startsWith("bundle.")) {
|
|
||||||
bundle_name = fileName;
|
|
||||||
}
|
|
||||||
return `<script src="${publicPath}${fileName}"${attrs}></script>`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const links = (files.css || [])
|
|
||||||
.map(({ fileName }) => {
|
|
||||||
const attrs = makeHtmlAttributes(attributes.link);
|
|
||||||
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
const metas = meta
|
|
||||||
.map((input) => {
|
|
||||||
const attrs = makeHtmlAttributes(input);
|
|
||||||
return `<meta${attrs}>`;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
return `
|
|
||||||
<!doctype html>
|
|
||||||
<html${makeHtmlAttributes(attributes.html)}>
|
|
||||||
<head>
|
|
||||||
${metas}
|
|
||||||
<title>${title}</title>
|
|
||||||
<link rel="stylesheet" href="${bundle_name.slice(0, -2)}css"/>
|
|
||||||
${links}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
${scripts}
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VIEWS.map((view) => ({
|
|
||||||
input: `src/pages/${view}/main.ts`,
|
|
||||||
output: {
|
|
||||||
dir: `build/${view}`,
|
|
||||||
entryFileNames: `bundle.[hash].min.js`,
|
|
||||||
format: "es",
|
|
||||||
sourcemap: true,
|
|
||||||
name: view,
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
svelte({
|
|
||||||
emitCss: true,
|
|
||||||
preprocess: sveltePreprocess({}),
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
esbuild({ sourceMap: dev, minify: true }),
|
|
||||||
html({
|
|
||||||
title: view,
|
|
||||||
attributes: {
|
|
||||||
html: { lang: "en" },
|
|
||||||
},
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
name: "viewport",
|
|
||||||
content: "width=device-width",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
template: htmlTemplate,
|
|
||||||
}),
|
|
||||||
resolve({
|
|
||||||
browser: true,
|
|
||||||
exportConditions: ["svelte"],
|
|
||||||
extensions: [".svelte"],
|
|
||||||
}),
|
|
||||||
image(),
|
|
||||||
sizes(),
|
|
||||||
visualizer({
|
|
||||||
filename: `build/stats/${view}.html`,
|
|
||||||
title: `Rullup bundle for ${view}`,
|
|
||||||
}),
|
|
||||||
postcss({
|
|
||||||
extract: true, // `bundle.css`, //TODO: Check if it should be enabled
|
|
||||||
// inject: true,
|
|
||||||
}),
|
|
||||||
hash({
|
|
||||||
dest: "bundle.[hash].min.js",
|
|
||||||
}),
|
|
||||||
// dev && livereload(),
|
|
||||||
],
|
|
||||||
}));
|
|
@ -1,94 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
// import { Tile } from "carbon-components-svelte";
|
|
||||||
|
|
||||||
export let title: string;
|
|
||||||
export let loading = false;
|
|
||||||
export let hide = false;
|
|
||||||
|
|
||||||
$: console.log({ loading });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="card-elevated container">
|
|
||||||
<!-- <div class="container card"> -->
|
|
||||||
<div class="card elv-8 title-container">
|
|
||||||
<h1 style="margin:0">{title}</h1>
|
|
||||||
</div>
|
|
||||||
{#if loading}
|
|
||||||
<div class="loader_container">
|
|
||||||
<div class="loader_box">
|
|
||||||
<div class="loader" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="content-container" class:loading_container={loading}>
|
|
||||||
{#if !(loading && hide)}
|
|
||||||
<slot />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.wrapper {
|
|
||||||
min-height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
border-radius: 4px;
|
|
||||||
position: relative;
|
|
||||||
padding-top: 2.5rem;
|
|
||||||
width: 25rem;
|
|
||||||
|
|
||||||
min-height: calc(100px + 2.5rem);
|
|
||||||
min-width: 100px;
|
|
||||||
|
|
||||||
margin-top: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-container {
|
|
||||||
margin: -4.8rem auto 0 auto;
|
|
||||||
max-width: 250px;
|
|
||||||
background-color: var(--primary);
|
|
||||||
color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
/* padding: 5px 20px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-container > h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container {
|
|
||||||
padding: 2em;
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 380px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading_container {
|
|
||||||
filter: blur(1px) opacity(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader_container {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,33 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
NavBrand,
|
|
||||||
NavHamburger,
|
|
||||||
NavLi,
|
|
||||||
NavUl,
|
|
||||||
Navbar,
|
|
||||||
} from "flowbite-svelte";
|
|
||||||
|
|
||||||
export let sidebarOpen: boolean;
|
|
||||||
export let sidebarOpenVisible: boolean;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Navbar let:hidden let:toggle color="form">
|
|
||||||
{#if sidebarOpenVisible}
|
|
||||||
<NavHamburger on:click={() => (sidebarOpen = !sidebarOpen)} />
|
|
||||||
{/if}
|
|
||||||
<NavBrand href="/">
|
|
||||||
<span
|
|
||||||
class="self-center whitespace-nowrap text-xl font-semibold dark:text-white"
|
|
||||||
>
|
|
||||||
OpenAuth
|
|
||||||
</span>
|
|
||||||
</NavBrand>
|
|
||||||
<NavHamburger on:click={toggle} />
|
|
||||||
<NavUl {hidden}>
|
|
||||||
<NavLi href="/" active={true}>Home</NavLi>
|
|
||||||
<NavLi href="/user">User</NavLi>
|
|
||||||
<!-- <NavLi href="/services">Services</NavLi>
|
|
||||||
<NavLi href="/pricing">Pricing</NavLi>
|
|
||||||
<NavLi href="/contact">Contact</NavLi> -->
|
|
||||||
</NavUl>
|
|
||||||
</Navbar>
|
|
@ -1,15 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
// import { onMount, afterUpdate, setContext } from "svelte";
|
|
||||||
// import { writable, derived } from "svelte/store";
|
|
||||||
|
|
||||||
// type Theme = "white" | "g10" | "g90" | "g100";
|
|
||||||
|
|
||||||
// export let persist: boolean = false;
|
|
||||||
// export let persistKey: string = "theme";
|
|
||||||
export let dark = false;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class={dark ? 'dark-theme' : 'light-theme'}>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
@ -1,42 +0,0 @@
|
|||||||
import "@hibas123/theme/out/base.css";
|
|
||||||
import "./theme.css";
|
|
||||||
import { default as Theme } from "./Theme.svelte";
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const elements = new WeakSet();
|
|
||||||
|
|
||||||
function check() {
|
|
||||||
document
|
|
||||||
.querySelectorAll(".floating>input")
|
|
||||||
.forEach((e: HTMLInputElement) => {
|
|
||||||
if (elements.has(e)) return;
|
|
||||||
elements.add(e);
|
|
||||||
|
|
||||||
function checkState() {
|
|
||||||
console.log("Check State");
|
|
||||||
if (e.value !== "") {
|
|
||||||
if (e.classList.contains("used")) return;
|
|
||||||
e.classList.add("used");
|
|
||||||
} else {
|
|
||||||
if (e.classList.contains("used")) e.classList.remove("used");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
e.addEventListener("change", () => checkState());
|
|
||||||
checkState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
check();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start observing the target node for configured mutations
|
|
||||||
observer.observe(window.document, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true,
|
|
||||||
});
|
|
||||||
check();
|
|
||||||
})();
|
|
||||||
|
|
||||||
export default Theme;
|
|
@ -1,257 +0,0 @@
|
|||||||
:root {
|
|
||||||
--primary: #1e88e5;
|
|
||||||
--mdc-theme-primary: var(--primary);
|
|
||||||
--mdc-theme-primary-bg: var(--mdc-theme--primary);
|
|
||||||
--mdc-theme-on-primary: white;
|
|
||||||
--error: #ff2f00;
|
|
||||||
--border-color: #ababab;
|
|
||||||
|
|
||||||
--default-font-size: 1.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: "Roboto", "Helvetica", sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
color: #636363;
|
|
||||||
position: relative;
|
|
||||||
background: #eee;
|
|
||||||
height: 100%;
|
|
||||||
font-size: var(--default-font-size);
|
|
||||||
min-width: 100vw;
|
|
||||||
min-height: 100vh;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group {
|
|
||||||
position: relative;
|
|
||||||
margin-top: 2rem;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
min-height: 45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating > input {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
padding: 10px 10px 10px 5px;
|
|
||||||
appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
display: block;
|
|
||||||
background: #fafafa;
|
|
||||||
background: unset;
|
|
||||||
color: #636363;
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
/* border-bottom: 1px solid #757575; */
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.floating > input:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Label */
|
|
||||||
|
|
||||||
.floating > label {
|
|
||||||
color: #999;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: normal;
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
left: 5px;
|
|
||||||
top: 10px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* active */
|
|
||||||
|
|
||||||
.floating > input:focus ~ label,
|
|
||||||
.floating > input.used ~ label {
|
|
||||||
top: -0.75em;
|
|
||||||
transform: scale(0.75);
|
|
||||||
left: -2px;
|
|
||||||
/* font-size: 14px; */
|
|
||||||
color: var(--primary);
|
|
||||||
transform-origin: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Underline */
|
|
||||||
|
|
||||||
.bar {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar:before,
|
|
||||||
.bar:after {
|
|
||||||
content: "";
|
|
||||||
height: 2px;
|
|
||||||
width: 0;
|
|
||||||
bottom: 1px;
|
|
||||||
position: absolute;
|
|
||||||
background: var(--primary);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar:before {
|
|
||||||
left: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar:after {
|
|
||||||
right: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* active */
|
|
||||||
|
|
||||||
.floating > input:focus ~ .bar:before,
|
|
||||||
.floating > input:focus ~ .bar:after {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlight */
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
position: absolute;
|
|
||||||
height: 60%;
|
|
||||||
width: 100px;
|
|
||||||
top: 25%;
|
|
||||||
left: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* active */
|
|
||||||
|
|
||||||
.floating > input:focus ~ .highlight {
|
|
||||||
animation: inputHighlighter 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animations */
|
|
||||||
|
|
||||||
@keyframes inputHighlighter {
|
|
||||||
from {
|
|
||||||
background: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
width: 0;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
margin: 2rem;
|
|
||||||
padding: 0 1em;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
border-width: 0;
|
|
||||||
outline: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
|
|
||||||
|
|
||||||
background-color: #cccccc;
|
|
||||||
color: #ecf0f1;
|
|
||||||
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
|
|
||||||
height: 48px;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.btn:focus {
|
|
||||||
filter: brightness(90%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn > * {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn span {
|
|
||||||
display: block;
|
|
||||||
padding: 12px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:before {
|
|
||||||
content: "";
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
width: 0;
|
|
||||||
padding-top: 0;
|
|
||||||
|
|
||||||
border-radius: 100%;
|
|
||||||
|
|
||||||
background-color: rgba(236, 240, 241, 0.3);
|
|
||||||
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
-moz-transform: translate(-50%, -50%);
|
|
||||||
-ms-transform: translate(-50%, -50%);
|
|
||||||
-o-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:active:before {
|
|
||||||
width: 120%;
|
|
||||||
padding-top: 120%;
|
|
||||||
|
|
||||||
transition: width 0.2s ease-out, padding-top 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-wide {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader_box {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader:after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
margin: 1px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 5px solid var(--primary);
|
|
||||||
border-color: var(--primary) transparent var(--primary) transparent;
|
|
||||||
animation: loader 1.2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loader {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Client } from "@hibas123/openauth-internalapi";
|
|
||||||
import request, { RequestError } from "./request";
|
|
||||||
|
|
||||||
const provider = new Client.ServiceProvider((data) => {
|
|
||||||
fetch("/api/jrpc", {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
}).then(res => {
|
|
||||||
if (res.ok) return res.json();
|
|
||||||
else throw new Error(res.statusText);
|
|
||||||
}).then(res => {
|
|
||||||
provider.onPacket(res);
|
|
||||||
}).catch(err => {
|
|
||||||
provider.onPacket({
|
|
||||||
jsonrpc: "2.0",
|
|
||||||
method: data.method,
|
|
||||||
id: data.id,
|
|
||||||
error: {
|
|
||||||
code: -32603,
|
|
||||||
message: err.message,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const InternalAPI = {
|
|
||||||
Account: new Client.AccountService(provider),
|
|
||||||
Security: new Client.SecurityService(provider),
|
|
||||||
TwoFactor: new Client.TFAService(provider),
|
|
||||||
Login: new Client.LoginService(provider),
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InternalAPI;
|
|
||||||
|
|
||||||
(window as any).InternalAPI = InternalAPI;
|
|
@ -1,20 +0,0 @@
|
|||||||
export function setCookie(cname: string, cvalue: string, exdate: string) {
|
|
||||||
const expires = exdate ? `;expires=${exdate}` : "";
|
|
||||||
document.cookie = `${cname}=${cvalue}${expires};path=/;`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCookie(cname: string) {
|
|
||||||
const name = cname + "=";
|
|
||||||
const dc = decodeURIComponent(document.cookie);
|
|
||||||
const ca = dc.split(";");
|
|
||||||
for (let i = 0; i < ca.length; i++) {
|
|
||||||
let c = ca[i];
|
|
||||||
while (c.charAt(0) == " ") {
|
|
||||||
c = c.substring(1);
|
|
||||||
}
|
|
||||||
if (c.indexOf(name) == 0) {
|
|
||||||
return c.substring(name.length, c.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
import { getCookie } from "./cookie";
|
|
||||||
|
|
||||||
const baseURL = "";
|
|
||||||
|
|
||||||
export class RequestError extends Error {
|
|
||||||
response: any;
|
|
||||||
constructor(message: string, response: any) {
|
|
||||||
super(message);
|
|
||||||
this.name = "RequestError";
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function request(
|
|
||||||
endpoint: string,
|
|
||||||
parameters: { [key: string]: string } = {},
|
|
||||||
method: "GET" | "POST" | "DELETE" | "PUT" = "GET",
|
|
||||||
body?: any,
|
|
||||||
authInParam = false,
|
|
||||||
redirect = false
|
|
||||||
) {
|
|
||||||
let pairs = [];
|
|
||||||
|
|
||||||
if (authInParam) {
|
|
||||||
parameters.login = getCookie("login");
|
|
||||||
parameters.special = getCookie("special");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let key in parameters) {
|
|
||||||
pairs.push(key + "=" + parameters[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = endpoint;
|
|
||||||
if (pairs.length > 0) {
|
|
||||||
url += "?" + pairs.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(baseURL + url, {
|
|
||||||
method,
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {
|
|
||||||
"content-type": "application/json",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((e) => {
|
|
||||||
if (e.status !== 200) throw new Error(e.statusText);
|
|
||||||
return e.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
if (data.error) {
|
|
||||||
if (redirect && data.additional && data.additional.auth) {
|
|
||||||
let state = btoa(
|
|
||||||
window.location.pathname + window.location.hash
|
|
||||||
);
|
|
||||||
window.location.href = `/login?state=${state}&base64=true`;
|
|
||||||
}
|
|
||||||
return Promise.reject(new RequestError(data.error, data));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,484 +0,0 @@
|
|||||||
var b;
|
|
||||||
if (!(b = t)) {
|
|
||||||
var w = Math,
|
|
||||||
y = {},
|
|
||||||
B = (y.p = {}),
|
|
||||||
aa = function () {},
|
|
||||||
C = (B.A = {
|
|
||||||
extend: function (o) {
|
|
||||||
aa.prototype = this;
|
|
||||||
var _ = new aa();
|
|
||||||
return o && _.u(o), (_.z = this), _;
|
|
||||||
},
|
|
||||||
create: function () {
|
|
||||||
var o = this.extend();
|
|
||||||
return o.h.apply(o, arguments), o;
|
|
||||||
},
|
|
||||||
h: function () {},
|
|
||||||
u: function (o) {
|
|
||||||
for (var _ in o) o.hasOwnProperty(_) && (this[_] = o[_]);
|
|
||||||
o.hasOwnProperty("toString") && (this.toString = o.toString);
|
|
||||||
},
|
|
||||||
e: function () {
|
|
||||||
return this.z.extend(this);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
D = (B.i = C.extend({
|
|
||||||
h: function (o, _) {
|
|
||||||
(o = this.d = o || []), (this.c = void 0 == _ ? 4 * o.length : _);
|
|
||||||
},
|
|
||||||
toString: function (o) {
|
|
||||||
return (o || ba).stringify(this);
|
|
||||||
},
|
|
||||||
concat: function (o) {
|
|
||||||
var _ = this.d,
|
|
||||||
Da = o.d,
|
|
||||||
Ea = this.c,
|
|
||||||
o = o.c;
|
|
||||||
if ((this.t(), Ea % 4))
|
|
||||||
for (var Fa = 0; Fa < o; Fa++)
|
|
||||||
_[(Ea + Fa) >>> 2] |=
|
|
||||||
(255 & (Da[Fa >>> 2] >>> (24 - 8 * (Fa % 4)))) <<
|
|
||||||
(24 - 8 * ((Ea + Fa) % 4));
|
|
||||||
else if (65535 < Da.length)
|
|
||||||
for (Fa = 0; Fa < o; Fa += 4) _[(Ea + Fa) >>> 2] = Da[Fa >>> 2];
|
|
||||||
else _.push.apply(_, Da);
|
|
||||||
return (this.c += o), this;
|
|
||||||
},
|
|
||||||
t: function () {
|
|
||||||
var o = this.d,
|
|
||||||
_ = this.c;
|
|
||||||
(o[_ >>> 2] &= 4294967295 << (32 - 8 * (_ % 4))),
|
|
||||||
(o.length = w.ceil(_ / 4));
|
|
||||||
},
|
|
||||||
e: function () {
|
|
||||||
var o = C.e.call(this);
|
|
||||||
return (o.d = this.d.slice(0)), o;
|
|
||||||
},
|
|
||||||
random: function (o) {
|
|
||||||
for (var _ = [], Da = 0; Da < o; Da += 4)
|
|
||||||
_.push(0 | (4294967296 * w.random()));
|
|
||||||
return D.create(_, o);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
ca = (y.O = {}),
|
|
||||||
ba = (ca.K = {
|
|
||||||
stringify: function (o) {
|
|
||||||
for (var Fa, _ = o.d, o = o.c, Da = [], Ea = 0; Ea < o; Ea++)
|
|
||||||
(Fa = 255 & (_[Ea >>> 2] >>> (24 - 8 * (Ea % 4)))),
|
|
||||||
Da.push((Fa >>> 4).toString(16)),
|
|
||||||
Da.push((15 & Fa).toString(16));
|
|
||||||
return Da.join("");
|
|
||||||
},
|
|
||||||
parse: function (o) {
|
|
||||||
for (var _ = o.length, Da = [], Ea = 0; Ea < _; Ea += 2)
|
|
||||||
Da[Ea >>> 3] |=
|
|
||||||
parseInt(o.substr(Ea, 2), 16) << (24 - 4 * (Ea % 8));
|
|
||||||
return D.create(Da, _ / 2);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
da = (ca.M = {
|
|
||||||
stringify: function (o) {
|
|
||||||
for (var _ = o.d, o = o.c, Da = [], Ea = 0; Ea < o; Ea++)
|
|
||||||
Da.push(
|
|
||||||
String.fromCharCode(
|
|
||||||
255 & (_[Ea >>> 2] >>> (24 - 8 * (Ea % 4)))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return Da.join("");
|
|
||||||
},
|
|
||||||
parse: function (o) {
|
|
||||||
for (var _ = o.length, Da = [], Ea = 0; Ea < _; Ea++)
|
|
||||||
Da[Ea >>> 2] |= (255 & o.charCodeAt(Ea)) << (24 - 8 * (Ea % 4));
|
|
||||||
return D.create(Da, _);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ea = (ca.N = {
|
|
||||||
stringify: function (o) {
|
|
||||||
try {
|
|
||||||
return decodeURIComponent(escape(da.stringify(o)));
|
|
||||||
} catch (_) {
|
|
||||||
throw Error("Malformed UTF-8 data");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parse: function (o) {
|
|
||||||
return da.parse(unescape(encodeURIComponent(o)));
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ia = (B.I = C.extend({
|
|
||||||
reset: function () {
|
|
||||||
(this.g = D.create()), (this.j = 0);
|
|
||||||
},
|
|
||||||
l: function (o) {
|
|
||||||
"string" == typeof o && (o = ea.parse(o)),
|
|
||||||
this.g.concat(o),
|
|
||||||
(this.j += o.c);
|
|
||||||
},
|
|
||||||
m: function (o) {
|
|
||||||
var _ = this.g,
|
|
||||||
Da = _.d,
|
|
||||||
Ea = _.c,
|
|
||||||
Fa = this.n,
|
|
||||||
Ga = Ea / (4 * Fa),
|
|
||||||
Ga = o ? w.ceil(Ga) : w.max((0 | Ga) - this.r, 0),
|
|
||||||
o = Ga * Fa,
|
|
||||||
Ea = w.min(4 * o, Ea);
|
|
||||||
if (o) {
|
|
||||||
for (var Ha = 0; Ha < o; Ha += Fa) this.H(Da, Ha);
|
|
||||||
(Ha = Da.splice(0, o)), (_.c -= Ea);
|
|
||||||
}
|
|
||||||
return D.create(Ha, Ea);
|
|
||||||
},
|
|
||||||
e: function () {
|
|
||||||
var o = C.e.call(this);
|
|
||||||
return (o.g = this.g.e()), o;
|
|
||||||
},
|
|
||||||
r: 0,
|
|
||||||
}));
|
|
||||||
B.B = ia.extend({
|
|
||||||
h: function () {
|
|
||||||
this.reset();
|
|
||||||
},
|
|
||||||
reset: function () {
|
|
||||||
ia.reset.call(this), this.q();
|
|
||||||
},
|
|
||||||
update: function (o) {
|
|
||||||
return this.l(o), this.m(), this;
|
|
||||||
},
|
|
||||||
o: function (o) {
|
|
||||||
return o && this.l(o), this.G(), this.f;
|
|
||||||
},
|
|
||||||
e: function () {
|
|
||||||
var o = ia.e.call(this);
|
|
||||||
return (o.f = this.f.e()), o;
|
|
||||||
},
|
|
||||||
n: 16,
|
|
||||||
D: function (o) {
|
|
||||||
return function (_, Da) {
|
|
||||||
return o.create(Da).o(_);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
F: function (o) {
|
|
||||||
return function (_, Da) {
|
|
||||||
return ja.J.create(o, Da).o(_);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
var ja = (y.s = {});
|
|
||||||
b = y;
|
|
||||||
}
|
|
||||||
var t = b,
|
|
||||||
K = t,
|
|
||||||
ka = K.p,
|
|
||||||
la = ka.A,
|
|
||||||
va = ka.i,
|
|
||||||
K = (K.w = {});
|
|
||||||
(K.C = la.extend({
|
|
||||||
h: function (o, _) {
|
|
||||||
(this.a = o), (this.b = _);
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
(K.i = la.extend({
|
|
||||||
h: function (o, _) {
|
|
||||||
(o = this.d = o || []), (this.c = void 0 == _ ? 8 * o.length : _);
|
|
||||||
},
|
|
||||||
v: function () {
|
|
||||||
for (var Fa, o = this.d, _ = o.length, Da = [], Ea = 0; Ea < _; Ea++)
|
|
||||||
(Fa = o[Ea]), Da.push(Fa.a), Da.push(Fa.b);
|
|
||||||
return va.create(Da, this.c);
|
|
||||||
},
|
|
||||||
e: function () {
|
|
||||||
for (
|
|
||||||
var o = la.e.call(this),
|
|
||||||
_ = (o.d = this.d.slice(0)),
|
|
||||||
Da = _.length,
|
|
||||||
Ea = 0;
|
|
||||||
Ea < Da;
|
|
||||||
Ea++
|
|
||||||
)
|
|
||||||
_[Ea] = _[Ea].e();
|
|
||||||
return o;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
function L() {
|
|
||||||
return wa.create.apply(wa, arguments);
|
|
||||||
}
|
|
||||||
for (
|
|
||||||
var xa = t.p.B,
|
|
||||||
M = t.w,
|
|
||||||
wa = M.C,
|
|
||||||
ya = M.i,
|
|
||||||
M = t.s,
|
|
||||||
za = [
|
|
||||||
L(1116352408, 3609767458),
|
|
||||||
L(1899447441, 602891725),
|
|
||||||
L(3049323471, 3964484399),
|
|
||||||
L(3921009573, 2173295548),
|
|
||||||
L(961987163, 4081628472),
|
|
||||||
L(1508970993, 3053834265),
|
|
||||||
L(2453635748, 2937671579),
|
|
||||||
L(2870763221, 3664609560),
|
|
||||||
L(3624381080, 2734883394),
|
|
||||||
L(310598401, 1164996542),
|
|
||||||
L(607225278, 1323610764),
|
|
||||||
L(1426881987, 3590304994),
|
|
||||||
L(1925078388, 4068182383),
|
|
||||||
L(2162078206, 991336113),
|
|
||||||
L(2614888103, 633803317),
|
|
||||||
L(3248222580, 3479774868),
|
|
||||||
L(3835390401, 2666613458),
|
|
||||||
L(4022224774, 944711139),
|
|
||||||
L(264347078, 2341262773),
|
|
||||||
L(604807628, 2007800933),
|
|
||||||
L(770255983, 1495990901),
|
|
||||||
L(1249150122, 1856431235),
|
|
||||||
L(1555081692, 3175218132),
|
|
||||||
L(1996064986, 2198950837),
|
|
||||||
L(2554220882, 3999719339),
|
|
||||||
L(2821834349, 766784016),
|
|
||||||
L(2952996808, 2566594879),
|
|
||||||
L(3210313671, 3203337956),
|
|
||||||
L(3336571891, 1034457026),
|
|
||||||
L(3584528711, 2466948901),
|
|
||||||
L(113926993, 3758326383),
|
|
||||||
L(338241895, 168717936),
|
|
||||||
L(666307205, 1188179964),
|
|
||||||
L(773529912, 1546045734),
|
|
||||||
L(1294757372, 1522805485),
|
|
||||||
L(1396182291, 2643833823),
|
|
||||||
L(1695183700, 2343527390),
|
|
||||||
L(1986661051, 1014477480),
|
|
||||||
L(2177026350, 1206759142),
|
|
||||||
L(2456956037, 344077627),
|
|
||||||
L(2730485921, 1290863460),
|
|
||||||
L(2820302411, 3158454273),
|
|
||||||
L(3259730800, 3505952657),
|
|
||||||
L(3345764771, 106217008),
|
|
||||||
L(3516065817, 3606008344),
|
|
||||||
L(3600352804, 1432725776),
|
|
||||||
L(4094571909, 1467031594),
|
|
||||||
L(275423344, 851169720),
|
|
||||||
L(430227734, 3100823752),
|
|
||||||
L(506948616, 1363258195),
|
|
||||||
L(659060556, 3750685593),
|
|
||||||
L(883997877, 3785050280),
|
|
||||||
L(958139571, 3318307427),
|
|
||||||
L(1322822218, 3812723403),
|
|
||||||
L(1537002063, 2003034995),
|
|
||||||
L(1747873779, 3602036899),
|
|
||||||
L(1955562222, 1575990012),
|
|
||||||
L(2024104815, 1125592928),
|
|
||||||
L(2227730452, 2716904306),
|
|
||||||
L(2361852424, 442776044),
|
|
||||||
L(2428436474, 593698344),
|
|
||||||
L(2756734187, 3733110249),
|
|
||||||
L(3204031479, 2999351573),
|
|
||||||
L(3329325298, 3815920427),
|
|
||||||
L(3391569614, 3928383900),
|
|
||||||
L(3515267271, 566280711),
|
|
||||||
L(3940187606, 3454069534),
|
|
||||||
L(4118630271, 4000239992),
|
|
||||||
L(116418474, 1914138554),
|
|
||||||
L(174292421, 2731055270),
|
|
||||||
L(289380356, 3203993006),
|
|
||||||
L(460393269, 320620315),
|
|
||||||
L(685471733, 587496836),
|
|
||||||
L(852142971, 1086792851),
|
|
||||||
L(1017036298, 365543100),
|
|
||||||
L(1126000580, 2618297676),
|
|
||||||
L(1288033470, 3409855158),
|
|
||||||
L(1501505948, 4234509866),
|
|
||||||
L(1607167915, 987167468),
|
|
||||||
L(1816402316, 1246189591),
|
|
||||||
],
|
|
||||||
$ = [],
|
|
||||||
Aa = 0;
|
|
||||||
80 > Aa;
|
|
||||||
Aa++
|
|
||||||
)
|
|
||||||
$[Aa] = L();
|
|
||||||
(M = M.k = xa.extend({
|
|
||||||
q: function () {
|
|
||||||
this.f = ya.create([
|
|
||||||
L(1779033703, 4089235720),
|
|
||||||
L(3144134277, 2227873595),
|
|
||||||
L(1013904242, 4271175723),
|
|
||||||
L(2773480762, 1595750129),
|
|
||||||
L(1359893119, 2917565137),
|
|
||||||
L(2600822924, 725511199),
|
|
||||||
L(528734635, 4215389547),
|
|
||||||
L(1541459225, 327033209),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
H: function (o, _) {
|
|
||||||
for (
|
|
||||||
var qb,
|
|
||||||
Da = this.f.d,
|
|
||||||
Ea = Da[0],
|
|
||||||
Fa = Da[1],
|
|
||||||
Ga = Da[2],
|
|
||||||
Ha = Da[3],
|
|
||||||
Ia = Da[4],
|
|
||||||
Ja = Da[5],
|
|
||||||
Ka = Da[6],
|
|
||||||
Da = Da[7],
|
|
||||||
La = Ea.a,
|
|
||||||
Ma = Ea.b,
|
|
||||||
Na = Fa.a,
|
|
||||||
Oa = Fa.b,
|
|
||||||
Pa = Ga.a,
|
|
||||||
Qa = Ga.b,
|
|
||||||
Ra = Ha.a,
|
|
||||||
Sa = Ha.b,
|
|
||||||
Ta = Ia.a,
|
|
||||||
Ua = Ia.b,
|
|
||||||
Va = Ja.a,
|
|
||||||
Wa = Ja.b,
|
|
||||||
Xa = Ka.a,
|
|
||||||
Ya = Ka.b,
|
|
||||||
Za = Da.a,
|
|
||||||
$a = Da.b,
|
|
||||||
_a = La,
|
|
||||||
ab = Ma,
|
|
||||||
bb = Na,
|
|
||||||
cb = Oa,
|
|
||||||
db = Pa,
|
|
||||||
eb = Qa,
|
|
||||||
fb = Ra,
|
|
||||||
gb = Sa,
|
|
||||||
hb = Ta,
|
|
||||||
ib = Ua,
|
|
||||||
jb = Va,
|
|
||||||
kb = Wa,
|
|
||||||
lb = Xa,
|
|
||||||
mb = Ya,
|
|
||||||
nb = Za,
|
|
||||||
ob = $a,
|
|
||||||
pb = 0;
|
|
||||||
80 > pb;
|
|
||||||
pb++
|
|
||||||
) {
|
|
||||||
if (((qb = $[pb]), 16 > pb))
|
|
||||||
var rb = (qb.a = 0 | o[_ + 2 * pb]),
|
|
||||||
sb = (qb.b = 0 | o[_ + 2 * pb + 1]);
|
|
||||||
else {
|
|
||||||
var rb = $[pb - 15],
|
|
||||||
sb = rb.a,
|
|
||||||
tb = rb.b,
|
|
||||||
rb =
|
|
||||||
((tb << 31) | (sb >>> 1)) ^
|
|
||||||
((tb << 24) | (sb >>> 8)) ^
|
|
||||||
(sb >>> 7),
|
|
||||||
tb =
|
|
||||||
((sb << 31) | (tb >>> 1)) ^
|
|
||||||
((sb << 24) | (tb >>> 8)) ^
|
|
||||||
((sb << 25) | (tb >>> 7)),
|
|
||||||
ub = $[pb - 2],
|
|
||||||
sb = ub.a,
|
|
||||||
vb = ub.b,
|
|
||||||
ub =
|
|
||||||
((vb << 13) | (sb >>> 19)) ^
|
|
||||||
((sb << 3) | (vb >>> 29)) ^
|
|
||||||
(sb >>> 6),
|
|
||||||
vb =
|
|
||||||
((sb << 13) | (vb >>> 19)) ^
|
|
||||||
((vb << 3) | (sb >>> 29)) ^
|
|
||||||
((sb << 26) | (vb >>> 6)),
|
|
||||||
sb = $[pb - 7],
|
|
||||||
wb = sb.a,
|
|
||||||
xb = $[pb - 16],
|
|
||||||
yb = xb.a,
|
|
||||||
xb = xb.b,
|
|
||||||
sb = tb + sb.b,
|
|
||||||
rb = rb + wb + (sb >>> 0 < tb >>> 0 ? 1 : 0),
|
|
||||||
sb = sb + vb,
|
|
||||||
rb = rb + ub + (sb >>> 0 < vb >>> 0 ? 1 : 0),
|
|
||||||
sb = sb + xb,
|
|
||||||
rb = rb + yb + (sb >>> 0 < xb >>> 0 ? 1 : 0);
|
|
||||||
(qb.a = rb), (qb.b = sb);
|
|
||||||
}
|
|
||||||
var wb = (hb & jb) ^ (~hb & lb),
|
|
||||||
xb = (ib & kb) ^ (~ib & mb),
|
|
||||||
qb = (_a & bb) ^ (_a & db) ^ (bb & db),
|
|
||||||
tb =
|
|
||||||
((ab << 4) | (_a >>> 28)) ^
|
|
||||||
((_a << 30) | (ab >>> 2)) ^
|
|
||||||
((_a << 25) | (ab >>> 7)),
|
|
||||||
ub =
|
|
||||||
((_a << 4) | (ab >>> 28)) ^
|
|
||||||
((ab << 30) | (_a >>> 2)) ^
|
|
||||||
((ab << 25) | (_a >>> 7)),
|
|
||||||
vb = za[pb],
|
|
||||||
Ab = vb.a,
|
|
||||||
Bb = vb.b,
|
|
||||||
vb =
|
|
||||||
ob +
|
|
||||||
(((hb << 18) | (ib >>> 14)) ^
|
|
||||||
((hb << 14) | (ib >>> 18)) ^
|
|
||||||
((ib << 23) | (hb >>> 9))),
|
|
||||||
yb =
|
|
||||||
nb +
|
|
||||||
(((ib << 18) | (hb >>> 14)) ^
|
|
||||||
((ib << 14) | (hb >>> 18)) ^
|
|
||||||
((hb << 23) | (ib >>> 9))) +
|
|
||||||
(vb >>> 0 < ob >>> 0 ? 1 : 0),
|
|
||||||
vb = vb + xb,
|
|
||||||
yb = yb + wb + (vb >>> 0 < xb >>> 0 ? 1 : 0),
|
|
||||||
vb = vb + Bb,
|
|
||||||
yb = yb + Ab + (vb >>> 0 < Bb >>> 0 ? 1 : 0),
|
|
||||||
vb = vb + sb,
|
|
||||||
yb = yb + rb + (vb >>> 0 < sb >>> 0 ? 1 : 0),
|
|
||||||
sb = ub + ((ab & cb) ^ (ab & eb) ^ (cb & eb)),
|
|
||||||
qb = tb + qb + (sb >>> 0 < ub >>> 0 ? 1 : 0),
|
|
||||||
nb = lb,
|
|
||||||
ob = mb,
|
|
||||||
lb = jb,
|
|
||||||
mb = kb,
|
|
||||||
jb = hb,
|
|
||||||
kb = ib,
|
|
||||||
ib = 0 | (gb + vb),
|
|
||||||
hb = 0 | (fb + yb + (ib >>> 0 < gb >>> 0 ? 1 : 0)),
|
|
||||||
fb = db,
|
|
||||||
gb = eb,
|
|
||||||
db = bb,
|
|
||||||
eb = cb,
|
|
||||||
bb = _a,
|
|
||||||
cb = ab,
|
|
||||||
ab = 0 | (vb + sb),
|
|
||||||
_a = 0 | (yb + qb + (ab >>> 0 < vb >>> 0 ? 1 : 0));
|
|
||||||
}
|
|
||||||
(Ma = Ea.b = 0 | (Ma + ab)),
|
|
||||||
(Ea.a = 0 | (La + _a + (Ma >>> 0 < ab >>> 0 ? 1 : 0))),
|
|
||||||
(Oa = Fa.b = 0 | (Oa + cb)),
|
|
||||||
(Fa.a = 0 | (Na + bb + (Oa >>> 0 < cb >>> 0 ? 1 : 0))),
|
|
||||||
(Qa = Ga.b = 0 | (Qa + eb)),
|
|
||||||
(Ga.a = 0 | (Pa + db + (Qa >>> 0 < eb >>> 0 ? 1 : 0))),
|
|
||||||
(Sa = Ha.b = 0 | (Sa + gb)),
|
|
||||||
(Ha.a = 0 | (Ra + fb + (Sa >>> 0 < gb >>> 0 ? 1 : 0))),
|
|
||||||
(Ua = Ia.b = 0 | (Ua + ib)),
|
|
||||||
(Ia.a = 0 | (Ta + hb + (Ua >>> 0 < ib >>> 0 ? 1 : 0))),
|
|
||||||
(Wa = Ja.b = 0 | (Wa + kb)),
|
|
||||||
(Ja.a = 0 | (Va + jb + (Wa >>> 0 < kb >>> 0 ? 1 : 0))),
|
|
||||||
(Ya = Ka.b = 0 | (Ya + mb)),
|
|
||||||
(Ka.a = 0 | (Xa + lb + (Ya >>> 0 < mb >>> 0 ? 1 : 0))),
|
|
||||||
($a = Da.b = 0 | ($a + ob)),
|
|
||||||
(Da.a = 0 | (Za + nb + ($a >>> 0 < ob >>> 0 ? 1 : 0)));
|
|
||||||
},
|
|
||||||
G: function () {
|
|
||||||
var o = this.g,
|
|
||||||
_ = o.d,
|
|
||||||
Da = 8 * this.j,
|
|
||||||
Ea = 8 * o.c;
|
|
||||||
(_[Ea >>> 5] |= 128 << (24 - (Ea % 32))),
|
|
||||||
(_[(((Ea + 128) >>> 10) << 5) + 31] = Da),
|
|
||||||
(o.c = 4 * _.length),
|
|
||||||
this.m(),
|
|
||||||
(this.f = this.f.v());
|
|
||||||
},
|
|
||||||
n: 32,
|
|
||||||
})),
|
|
||||||
(t.k = xa.D(M)),
|
|
||||||
(t.L = xa.F(M));
|
|
||||||
export default function sha512(o) {
|
|
||||||
return t.k(o) + "";
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
/* material-icons-regular - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
|
||||||
font-family: "Material Icons";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url("/static/material-icons-v140-latin-regular.woff2") format("woff2"),
|
|
||||||
/* Chrome 36+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/static/material-icons-v140-latin-regular.woff") format("woff"); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* material-icons-outlined-regular - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
|
||||||
font-family: "Material Icons Outlined";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url("/static/material-icons-outlined-v109-latin-regular.woff2")
|
|
||||||
format("woff2"),
|
|
||||||
/* Chrome 36+, Opera 23+, Firefox 39+ */
|
|
||||||
url("/static/material-icons-outlined-v109-latin-regular.woff")
|
|
||||||
format("woff"); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-icons {
|
|
||||||
font-family: "Material Icons";
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 24px; /* Preferred icon size */
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1;
|
|
||||||
text-transform: none;
|
|
||||||
letter-spacing: normal;
|
|
||||||
word-wrap: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
direction: ltr;
|
|
||||||
|
|
||||||
/* Support for all WebKit browsers. */
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
/* Support for Safari and Chrome. */
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
|
|
||||||
/* Support for Firefox. */
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
|
|
||||||
/* Support for IE. */
|
|
||||||
font-feature-settings: "liga";
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-icons-outlined {
|
|
||||||
font-family: "Material Icons Outlined";
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 24px;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: normal;
|
|
||||||
text-transform: none;
|
|
||||||
display: inline-block;
|
|
||||||
white-space: nowrap;
|
|
||||||
word-wrap: normal;
|
|
||||||
direction: ltr;
|
|
||||||
-webkit-font-feature-settings: "liga";
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<div class="main">
|
|
||||||
<h1>Home Page</h1>
|
|
||||||
|
|
||||||
<h2>About</h2>
|
|
||||||
<p>
|
|
||||||
OpenAuth is a Service to provide simple Authentication to a veriaty of
|
|
||||||
Applications. With a simple to use API and different Strategies, it can be
|
|
||||||
easily integrated into most Applications.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>QickLinks</h2>
|
|
||||||
<p>
|
|
||||||
If you want to manage your Account, click
|
|
||||||
<a href="user.html">here</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Applications using OpenAuth</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style: none;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,8 +0,0 @@
|
|||||||
import "../../components/theme";
|
|
||||||
import App from "./App.svelte";
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.body,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
@ -1,34 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {} from "flowbite-svelte";
|
|
||||||
|
|
||||||
import { LoginState } from "@hibas123/openauth-internalapi";
|
|
||||||
import Theme from "../../components/theme";
|
|
||||||
import loginState from "./state";
|
|
||||||
import HoveringContentBox from "../../components/HoveringContentBox.svelte";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import Username from "./Username.svelte";
|
|
||||||
import Password from "./Password.svelte";
|
|
||||||
import Success from "./Success.svelte";
|
|
||||||
import TwoFactor from "./TwoFactor.svelte";
|
|
||||||
|
|
||||||
const { state } = loginState;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Theme>
|
|
||||||
<HoveringContentBox title="Login" loading={$state.loading}>
|
|
||||||
<form action="JavaScript:void(0)">
|
|
||||||
{#if $state.success}
|
|
||||||
<Success />
|
|
||||||
{:else if !$state.username}
|
|
||||||
<Username on:username={(evt) => loginState.setUsername(evt.detail)} />
|
|
||||||
{:else if !$state.password}
|
|
||||||
<Password
|
|
||||||
username={$state.username}
|
|
||||||
on:password={(evt) => loginState.setPassword(evt.detail)}
|
|
||||||
/>
|
|
||||||
{:else if $state.requireTwoFactor.length > 0}
|
|
||||||
<TwoFactor />
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
||||||
</HoveringContentBox>
|
|
||||||
</Theme>
|
|
@ -1,16 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import loginState from "./state";
|
|
||||||
|
|
||||||
let { state } = loginState;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $state.error}
|
|
||||||
<div class="error">{$state.error}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.error {
|
|
||||||
color: var(--error);
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,30 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import Error from "./Error.svelte";
|
|
||||||
|
|
||||||
let password: string = "";
|
|
||||||
export let username: string;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Enter the password for {username}</h3>
|
|
||||||
<div class="floating group">
|
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
type="password"
|
|
||||||
autocomplete="password"
|
|
||||||
autofocus
|
|
||||||
bind:value={password}
|
|
||||||
/>
|
|
||||||
<span class="highlight" />
|
|
||||||
<span class="bar" />
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<Error />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-wide"
|
|
||||||
on:click={() => dispatch("password", password)}>Next</button
|
|
||||||
>
|
|
@ -1,99 +0,0 @@
|
|||||||
<script>
|
|
||||||
// import {
|
|
||||||
// onMount,
|
|
||||||
// onDestroy
|
|
||||||
// } from "svelte";
|
|
||||||
import { onMount, onDestroy } from "svelte";
|
|
||||||
|
|
||||||
const basetext = "Logged in. Redirecting";
|
|
||||||
let dots = 0;
|
|
||||||
|
|
||||||
$: text = basetext + ".".repeat(dots);
|
|
||||||
|
|
||||||
let iv;
|
|
||||||
onMount(() => {
|
|
||||||
console.log("Mounted");
|
|
||||||
iv = setInterval(() => {
|
|
||||||
dots++;
|
|
||||||
if (dots > 3) dots = 0;
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
console.log("on Destroy");
|
|
||||||
clearInterval(iv);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.checkmark__circle {
|
|
||||||
stroke-dasharray: 166;
|
|
||||||
stroke-dashoffset: 166;
|
|
||||||
stroke-width: 2;
|
|
||||||
stroke-miterlimit: 10;
|
|
||||||
stroke: #7ac142;
|
|
||||||
fill: none;
|
|
||||||
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkmark {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: block;
|
|
||||||
stroke-width: 2;
|
|
||||||
stroke: #fff;
|
|
||||||
stroke-miterlimit: 10;
|
|
||||||
margin: 10% auto;
|
|
||||||
box-shadow: inset 0px 0px 0px #7ac142;
|
|
||||||
animation: fill 0.4s ease-in-out 0.4s forwards,
|
|
||||||
scale 0.3s ease-in-out 0.9s both;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkmark__check {
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
stroke-dasharray: 48;
|
|
||||||
stroke-dashoffset: 48;
|
|
||||||
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes stroke {
|
|
||||||
100% {
|
|
||||||
stroke-dashoffset: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scale {
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: scale3d(1.1, 1.1, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fill {
|
|
||||||
100% {
|
|
||||||
box-shadow: inset 0px 0px 0px 30px #7ac142;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scale {
|
|
||||||
transform: scale(1.5);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="scale">
|
|
||||||
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
|
||||||
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" />
|
|
||||||
<path
|
|
||||||
class="checkmark__check"
|
|
||||||
fill="none"
|
|
||||||
d="M14.1 27.2l7.1 7.2 16.7-16.8" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<!-- <div style="text-align: center;"> -->
|
|
||||||
<h3>{text}</h3>
|
|
||||||
<!-- </div> -->
|
|
@ -1,27 +0,0 @@
|
|||||||
<script>
|
|
||||||
import Cleave from "cleave.js";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import Error from "../Error.svelte";
|
|
||||||
|
|
||||||
// export let label;
|
|
||||||
export let value;
|
|
||||||
export let length = 6;
|
|
||||||
|
|
||||||
let input;
|
|
||||||
onMount(() => {
|
|
||||||
const cleaveCustom = new Cleave(input, {
|
|
||||||
blocks: [length / 2, length / 2],
|
|
||||||
delimiter: " ",
|
|
||||||
numericOnly: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="floating group">
|
|
||||||
<input id="code-input" bind:this={input} autofocus bind:value />
|
|
||||||
<span class="highlight" />
|
|
||||||
<span class="bar" />
|
|
||||||
<label for="code-input">Code</label>
|
|
||||||
|
|
||||||
<Error />
|
|
||||||
</div>
|
|
@ -1,21 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Error from "../Error.svelte";
|
|
||||||
import loginState from "../state";
|
|
||||||
import CodeInput from "./CodeInput.svelte";
|
|
||||||
|
|
||||||
export let id: string;
|
|
||||||
export let name: string;
|
|
||||||
|
|
||||||
let code: string = "";
|
|
||||||
|
|
||||||
function send() {
|
|
||||||
loginState.useTOTP(id, code.replace(/\s+/g, ""));
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>TOTP {name}</h3>
|
|
||||||
<CodeInput bind:value={code} length={6} />
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<button class="btn btn-primary btn-wide" on:click={send}> Send </button>
|
|
||||||
</div>
|
|
@ -1,28 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import Error from "../Error.svelte";
|
|
||||||
import loginState from "../state";
|
|
||||||
import { startAuthentication } from "@simplewebauthn/browser";
|
|
||||||
|
|
||||||
export let id: string;
|
|
||||||
|
|
||||||
async function doAuth() {
|
|
||||||
let challenge = await loginState.getWebAuthnChallenge(id);
|
|
||||||
try {
|
|
||||||
loginState.setLoading(true);
|
|
||||||
let result = await startAuthentication(JSON.parse(challenge));
|
|
||||||
await loginState.useWebAuthn(id, result);
|
|
||||||
} catch (e) {
|
|
||||||
loginState.setError(e.message);
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
loginState.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
doAuth();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Error />
|
|
@ -1,114 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import loginState from "./state";
|
|
||||||
import Icon from "./icons/Icon.svelte";
|
|
||||||
import { TFAType } from "@hibas123/openauth-internalapi";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import Totp from "./TF/TOTP.svelte";
|
|
||||||
import Error from "./Error.svelte";
|
|
||||||
import WebAuthn from "./TF/WebAuthn.svelte";
|
|
||||||
|
|
||||||
let { state } = loginState;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let selected = undefined;
|
|
||||||
|
|
||||||
$: {
|
|
||||||
if ($state.requireTwoFactor?.length == 1) {
|
|
||||||
selected = $state.requireTwoFactor[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeIconMap = {
|
|
||||||
[TFAType.TOTP]: "Authenticator",
|
|
||||||
[TFAType.BACKUP_CODE]: "BackupCode",
|
|
||||||
[TFAType.WEBAUTHN]: "SecurityKey",
|
|
||||||
[TFAType.APP_ALLOW]: "AppPush",
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if !selected}
|
|
||||||
<h3>Choose your 2FA method</h3>
|
|
||||||
<ul>
|
|
||||||
{#each $state.requireTwoFactor ?? [] as method}
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<li on:click={() => (selected = method)}>
|
|
||||||
<div class="icon">
|
|
||||||
<Icon icon_name={typeIconMap[method.tfatype]} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="name">{method.name}</div>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<Error />
|
|
||||||
</ul>
|
|
||||||
{:else}
|
|
||||||
{#if selected.tfatype == TFAType.TOTP}
|
|
||||||
<Totp id={selected.id} name={selected.name} />
|
|
||||||
{:else if selected.tfatype == TFAType.BACKUP_CODE}
|
|
||||||
backup
|
|
||||||
{:else if selected.tfatype == TFAType.WEBAUTHN}
|
|
||||||
<WebAuthn id={selected.id} />
|
|
||||||
{:else if selected.tfatype == TFAType.APP_ALLOW}
|
|
||||||
appallow
|
|
||||||
{:else}
|
|
||||||
<p>Unknown 2FA type</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a
|
|
||||||
class="to-list"
|
|
||||||
href="# "
|
|
||||||
on:click={(evt) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
loginState.setError(undefined);
|
|
||||||
selected = undefined;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Choose another Method
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-inline-start: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
border-top: 1px grey solid;
|
|
||||||
padding: 1em;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:hover {
|
|
||||||
background-color: #e2e2e2;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:first-child {
|
|
||||||
border-top: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
height: 1.5rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
margin-left: 1rem;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
font-size: 20px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.to-list {
|
|
||||||
color: var(--primary);
|
|
||||||
text-decoration: none;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,29 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import Error from "./Error.svelte";
|
|
||||||
|
|
||||||
let username: string = "";
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h3>Enter your Username or your E-Mail Address</h3>
|
|
||||||
<div class="floating group">
|
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
|
||||||
<input
|
|
||||||
id="username"
|
|
||||||
type="text"
|
|
||||||
autocomplete="username"
|
|
||||||
autofocus
|
|
||||||
bind:value={username}
|
|
||||||
/>
|
|
||||||
<span class="highlight" />
|
|
||||||
<span class="bar" />
|
|
||||||
<label for="username">Username or E-Mail</label>
|
|
||||||
<Error />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-wide"
|
|
||||||
on:click={() => dispatch("username", username)}>Next</button
|
|
||||||
>
|
|
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18.617,1.72c0,-0.949 -0.771,-1.72 -1.721,-1.72l-9.792,0c-0.95,0 -1.721,0.771 -1.721,1.72l0,20.56c0,0.949 0.771,1.72 1.721,1.72l9.792,0c0.95,0 1.721,-0.771 1.721,-1.72l0,-20.56Z" style="fill:#4d4d4d;"/><rect x="6" y="3" width="12" height="18" style="fill:#b3b3b3;"/><path d="M14,1.5c0,-0.129 -0.105,-0.233 -0.233,-0.233l-3.534,0c-0.128,0 -0.233,0.104 -0.233,0.233c0,0.129 0.105,0.233 0.233,0.233l3.534,0c0.128,0 0.233,-0.104 0.233,-0.233Z" style="fill:#b3b3b3;"/><ellipse cx="12" cy="22.5" rx="0.983" ry="1" style="fill:#b3b3b3;"/></svg>
|
|
Before Width: | Height: | Size: 992 B |
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><path d="M18.5,12c0,3.59 -2.91,6.5 -6.5,6.5c-3.59,0 -6.5,-2.91 -6.5,-6.5c0,-3.59 2.91,-6.5 6.5,-6.5c1.729,0 3.295,0.679 4.46,1.78l4.169,-3.599c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5.5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle id="XMLID_1331_" cx="12" cy="12" r="12" style="fill:#808080;"/><path d="M19,12c0,3.866 -3.134,7 -7,7c-3.866,0 -7,-3.134 -7,-7c0,-3.866 3.134,-7 7,-7c1.88,0 3.583,0.745 4.841,1.951l3.788,-3.27c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle cx="12" cy="2.5" r="1" style="fill:#b3b3b3;"/><circle cx="12" cy="21.5" r="1" style="fill:#b3b3b3;"/><circle cx="2.5" cy="12" r="1" style="fill:#b3b3b3;"/><path d="M4.575,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M18.01,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M4.575,4.575c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><circle id="XMLID_1329_" cx="12" cy="12" r="6" style="fill:#808080;"/><circle id="XMLID_1330_" cx="12" cy="12" r="7" style="fill:#808080;"/><path d="M19,12.25c0,-0.042 -0.006,-0.083 -0.006,-0.125c-0.068,3.808 -3.17,6.875 -6.994,6.875c-3.824,0 -6.933,-3.067 -7,-6.875c-0.001,0.042 0,0.083 0,0.125c0,3.866 3.134,7 7,7c3.866,0 7,-3.134 7,-7Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M18.92,13l-3.061,0c0.083,-0.321 0.141,-0.653 0.141,-1c0,-2.209 -1.791,-4 -4,-4c-2.209,0 -4,1.791 -4,4c0,1.105 0.448,2.105 1.172,2.828c1.014,1.015 4.057,4.058 4.057,4.058c2.955,-0.525 5.263,-2.899 5.691,-5.886Z" style="fill:#4d4d4d;fill-rule:nonzero;"/><path d="M22,13l-10,0c-0.553,0 -1,-0.448 -1,-1c0,-0.552 0.447,-1 1,-1l10,0c0.553,0 1,0.448 1,1c0,0.552 -0.447,1 -1,1Z" style="fill:#b3b3b3;fill-rule:nonzero;"/><path d="M11.948,11.25l10.104,0c0.409,0 0.776,0.247 0.935,0.592c-0.08,-0.471 -0.492,-0.842 -0.987,-0.842l-10,0c-0.495,0 -0.9,0.33 -0.98,0.801c0.159,-0.345 0.519,-0.551 0.928,-0.551Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M23,12c0,0.552 -0.447,1 -1,1l-3.08,0c-0.428,2.988 -2.737,5.362 -5.693,5.886l3.935,3.946c4.04,-1.931 6.838,-6.056 6.838,-10.832l-1,0Z" style="fill:#666;fill-opacity:0.5;fill-rule:nonzero;"/><path d="M12,5c-3.866,0 -7,3.134 -7,7c0,0.042 -0.001,0.069 0,0.111c0.067,-3.808 3.176,-6.861 7,-6.861c2.828,0 4.841,1.701 4.841,1.701c-1.257,-1.198 -2.968,-1.951 -4.841,-1.951Z" style="fill-opacity:0.1;fill-rule:nonzero;"/><circle id="XMLID_4_" cx="12" cy="12" r="12" style="fill:url(#_Linear1);"/></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(21.7566,10.1453,-10.1453,21.7566,1.12171,6.92737)"><stop offset="0" style="stop-color:#fff;stop-opacity:0.2"/><stop offset="1" style="stop-color:#fff;stop-opacity:0"/></linearGradient></defs></svg>
|
|
Before Width: | Height: | Size: 3.6 KiB |
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><path d="M20.562,9.105c0,-0.853 -0.692,-1.544 -1.544,-1.544l-14.036,0c-0.852,0 -1.544,0.691 -1.544,1.544l0,12.351c0,0.852 0.692,1.544 1.544,1.544l14.036,0c0.852,0 1.544,-0.692 1.544,-1.544l0,-12.351Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/><circle cx="12" cy="15.3" r="1.5"/><path d="M16.646,4.28c0,-1.81 -1.47,-3.28 -3.28,-3.28l-2.732,0c-1.81,0 -3.28,1.47 -3.28,3.28l0,3.281l9.292,0l0,-3.281Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/></svg>
|
|
Before Width: | Height: | Size: 927 B |
@ -1,15 +0,0 @@
|
|||||||
<script>
|
|
||||||
export let icon_name;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if icon_name === "SecurityKey"}
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18,7.692c0,-0.925 -0.751,-1.675 -1.675,-1.675l-14.65,0c-0.924,0 -1.675,0.75 -1.675,1.675l0,8.616c0,0.925 0.751,1.675 1.675,1.675l14.65,0c0.924,0 1.675,-0.75 1.675,-1.675l0,-8.616Z" style="fill:#4d4d4d;"/><rect x="18" y="8.011" width="6" height="7.978" style="fill:#4d4d4d;"/><rect x="18" y="10.644" width="4.8" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="12.229" width="4.8" height="1.164" style="fill:#b3b3b3;"/><rect x="18" y="9.008" width="5.25" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="13.794" width="5.25" height="1.197" style="fill:#b3b3b3;"/></svg>
|
|
||||||
{:else if icon_name === "Authenticator"}
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><path d="M18.5,12c0,3.59 -2.91,6.5 -6.5,6.5c-3.59,0 -6.5,-2.91 -6.5,-6.5c0,-3.59 2.91,-6.5 6.5,-6.5c1.729,0 3.295,0.679 4.46,1.78l4.169,-3.599c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5.5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle id="XMLID_1331_" cx="12" cy="12" r="12" style="fill:#808080;"/><path d="M19,12c0,3.866 -3.134,7 -7,7c-3.866,0 -7,-3.134 -7,-7c0,-3.866 3.134,-7 7,-7c1.88,0 3.583,0.745 4.841,1.951l3.788,-3.27c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle cx="12" cy="2.5" r="1" style="fill:#b3b3b3;"/><circle cx="12" cy="21.5" r="1" style="fill:#b3b3b3;"/><circle cx="2.5" cy="12" r="1" style="fill:#b3b3b3;"/><path d="M4.575,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M18.01,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M4.575,4.575c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><circle id="XMLID_1329_" cx="12" cy="12" r="6" style="fill:#808080;"/><circle id="XMLID_1330_" cx="12" cy="12" r="7" style="fill:#808080;"/><path d="M19,12.25c0,-0.042 -0.006,-0.083 -0.006,-0.125c-0.068,3.808 -3.17,6.875 -6.994,6.875c-3.824,0 -6.933,-3.067 -7,-6.875c-0.001,0.042 0,0.083 0,0.125c0,3.866 3.134,7 7,7c3.866,0 7,-3.134 7,-7Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M18.92,13l-3.061,0c0.083,-0.321 0.141,-0.653 0.141,-1c0,-2.209 -1.791,-4 -4,-4c-2.209,0 -4,1.791 -4,4c0,1.105 0.448,2.105 1.172,2.828c1.014,1.015 4.057,4.058 4.057,4.058c2.955,-0.525 5.263,-2.899 5.691,-5.886Z" style="fill:#4d4d4d;fill-rule:nonzero;"/><path d="M22,13l-10,0c-0.553,0 -1,-0.448 -1,-1c0,-0.552 0.447,-1 1,-1l10,0c0.553,0 1,0.448 1,1c0,0.552 -0.447,1 -1,1Z" style="fill:#b3b3b3;fill-rule:nonzero;"/><path d="M11.948,11.25l10.104,0c0.409,0 0.776,0.247 0.935,0.592c-0.08,-0.471 -0.492,-0.842 -0.987,-0.842l-10,0c-0.495,0 -0.9,0.33 -0.98,0.801c0.159,-0.345 0.519,-0.551 0.928,-0.551Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M23,12c0,0.552 -0.447,1 -1,1l-3.08,0c-0.428,2.988 -2.737,5.362 -5.693,5.886l3.935,3.946c4.04,-1.931 6.838,-6.056 6.838,-10.832l-1,0Z" style="fill:#666;fill-opacity:0.5;fill-rule:nonzero;"/><path d="M12,5c-3.866,0 -7,3.134 -7,7c0,0.042 -0.001,0.069 0,0.111c0.067,-3.808 3.176,-6.861 7,-6.861c2.828,0 4.841,1.701 4.841,1.701c-1.257,-1.198 -2.968,-1.951 -4.841,-1.951Z" style="fill-opacity:0.1;fill-rule:nonzero;"/><circle id="XMLID_4_" cx="12" cy="12" r="12" style="fill:url(#_Linear1);"/></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(21.7566,10.1453,-10.1453,21.7566,1.12171,6.92737)"><stop offset="0" style="stop-color:#fff;stop-opacity:0.2"/><stop offset="1" style="stop-color:#fff;stop-opacity:0"/></linearGradient></defs></svg>
|
|
||||||
{:else if icon_name === "BackupCode"}
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><path d="M20.562,9.105c0,-0.853 -0.692,-1.544 -1.544,-1.544l-14.036,0c-0.852,0 -1.544,0.691 -1.544,1.544l0,12.351c0,0.852 0.692,1.544 1.544,1.544l14.036,0c0.852,0 1.544,-0.692 1.544,-1.544l0,-12.351Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/><circle cx="12" cy="15.3" r="1.5"/><path d="M16.646,4.28c0,-1.81 -1.47,-3.28 -3.28,-3.28l-2.732,0c-1.81,0 -3.28,1.47 -3.28,3.28l0,3.281l9.292,0l0,-3.281Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/></svg>
|
|
||||||
{:else if icon_name === "AppPush"}
|
|
||||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18.617,1.72c0,-0.949 -0.771,-1.72 -1.721,-1.72l-9.792,0c-0.95,0 -1.721,0.771 -1.721,1.72l0,20.56c0,0.949 0.771,1.72 1.721,1.72l9.792,0c0.95,0 1.721,-0.771 1.721,-1.72l0,-20.56Z" style="fill:#4d4d4d;"/><rect x="6" y="3" width="12" height="18" style="fill:#b3b3b3;"/><path d="M14,1.5c0,-0.129 -0.105,-0.233 -0.233,-0.233l-3.534,0c-0.128,0 -0.233,0.104 -0.233,0.233c0,0.129 0.105,0.233 0.233,0.233l3.534,0c0.128,0 0.233,-0.104 0.233,-0.233Z" style="fill:#b3b3b3;"/><ellipse cx="12" cy="22.5" rx="0.983" ry="1" style="fill:#b3b3b3;"/></svg>
|
|
||||||
{:else}
|
|
||||||
ERR
|
|
||||||
{/if}
|
|
@ -1 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18,7.692c0,-0.925 -0.751,-1.675 -1.675,-1.675l-14.65,0c-0.924,0 -1.675,0.75 -1.675,1.675l0,8.616c0,0.925 0.751,1.675 1.675,1.675l14.65,0c0.924,0 1.675,-0.75 1.675,-1.675l0,-8.616Z" style="fill:#4d4d4d;"/><rect x="18" y="8.011" width="6" height="7.978" style="fill:#4d4d4d;"/><rect x="18" y="10.644" width="4.8" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="12.229" width="4.8" height="1.164" style="fill:#b3b3b3;"/><rect x="18" y="9.008" width="5.25" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="13.794" width="5.25" height="1.197" style="fill:#b3b3b3;"/></svg>
|
|
Before Width: | Height: | Size: 1.0 KiB |
@ -1,5 +0,0 @@
|
|||||||
import App from "./App.svelte";
|
|
||||||
|
|
||||||
new App({
|
|
||||||
target: document.body,
|
|
||||||
});
|
|
@ -1,183 +0,0 @@
|
|||||||
import type { LoginState } from "@hibas123/openauth-internalapi";
|
|
||||||
import { derived, get, writable } from "svelte/store";
|
|
||||||
import InternalAPI from "../../helper/api";
|
|
||||||
import sha from "../../helper/sha512";
|
|
||||||
|
|
||||||
interface LocalLoginState extends LoginState {
|
|
||||||
loading: boolean;
|
|
||||||
error?: string;
|
|
||||||
username?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginStore {
|
|
||||||
state = writable<LocalLoginState>({
|
|
||||||
username: undefined,
|
|
||||||
password: false,
|
|
||||||
passwordSalt: undefined,
|
|
||||||
requireTwoFactor: [],
|
|
||||||
success: false,
|
|
||||||
loading: true,
|
|
||||||
error: undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
isFinished = derived(this.state, $state => $state.success);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.state.subscribe((state) => {
|
|
||||||
if (state.success) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.finish();
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(loading: boolean) {
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
loading,
|
|
||||||
error: loading ? undefined : current.error,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
setError(error: string) {
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
error,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async getState() {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
let state = await InternalAPI.Login.GetState();
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
...state,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setUsername(username: string) {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
let state = await InternalAPI.Login.Start(username);
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
...state,
|
|
||||||
username
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async setPassword(password: string) {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
const date = new Date().valueOf();
|
|
||||||
let salt = get(this.state).passwordSalt
|
|
||||||
let pw = sha(sha(salt + password) + date.toString());
|
|
||||||
|
|
||||||
let state = await InternalAPI.Login.UsePassword(pw, date);
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
...state,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async useTOTP(id: string, code: string) {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
let state = await InternalAPI.Login.UseTOTP(id, code);
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
...state,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async useBackupCode(id: string, code: string) {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
let state = await InternalAPI.Login.UseBackupCode(id, code);
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
...state,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWebAuthnChallenge(id: string) {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
let challenge = await InternalAPI.Login.GetWebAuthnChallenge(id);
|
|
||||||
return challenge;
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async useWebAuthn(id: string, response: any) {
|
|
||||||
try {
|
|
||||||
this.setLoading(true);
|
|
||||||
|
|
||||||
let state = await InternalAPI.Login.UseWebAuthn(id, JSON.stringify(response));
|
|
||||||
this.state.update(current => ({
|
|
||||||
...current,
|
|
||||||
...state,
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
this.setError(err.message);
|
|
||||||
} finally {
|
|
||||||
this.setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async finish() {
|
|
||||||
let url = new URL(window.location.href);
|
|
||||||
let state = url.searchParams.get("state");
|
|
||||||
let red = "/";
|
|
||||||
|
|
||||||
if (state) {
|
|
||||||
let base64 = url.searchParams.get("base64");
|
|
||||||
if (base64) red = atob(state);
|
|
||||||
else red = state;
|
|
||||||
}
|
|
||||||
setTimeout(() => (window.location.href = red), 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginState = new LoginStore();
|
|
||||||
|
|
||||||
export default loginState;
|
|
@ -1,59 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import HoveringContentBox from "../../components/HoveringContentBox.svelte";
|
|
||||||
import Theme from "../../components/theme/Theme.svelte";
|
|
||||||
|
|
||||||
export let loading = true;
|
|
||||||
export let appName = "";
|
|
||||||
export let permissions: any[] = [];
|
|
||||||
export let accept: () => void;
|
|
||||||
|
|
||||||
const base_perm = {
|
|
||||||
name: "Access Profile",
|
|
||||||
description:
|
|
||||||
"Access your identity and some basic informations like your username",
|
|
||||||
};
|
|
||||||
|
|
||||||
$: view_perms = [base_perm, ...permissions];
|
|
||||||
|
|
||||||
$: console.log({ loading, appName, permissions, accept });
|
|
||||||
|
|
||||||
function deny() {
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Theme dark={false}>
|
|
||||||
<HoveringContentBox title="Authorize" {loading} hide>
|
|
||||||
<div class="title margin">
|
|
||||||
<h2 style="font-weight: normal">
|
|
||||||
Grant
|
|
||||||
<span id="hostname" style="font-weight: bold;">{appName}</span>
|
|
||||||
the following permissions?
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="list list-divider">
|
|
||||||
{#each view_perms as permission (permission._íd)}
|
|
||||||
<li class="permission">
|
|
||||||
<h3>{permission.name}</h3>
|
|
||||||
<p>{permission.description}</p>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div style="text-align: right;">
|
|
||||||
<button class="btn btn-primary" on:click={accept}>Allow</button>
|
|
||||||
<button class="btn btn-primary" on:click={deny}>Deny</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HoveringContentBox>
|
|
||||||
</Theme>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.permission > h3 {
|
|
||||||
}
|
|
||||||
|
|
||||||
.permission > p {
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,162 +0,0 @@
|
|||||||
import "../../components/theme";
|
|
||||||
import App from "./App.svelte";
|
|
||||||
import request from "../../helper/request";
|
|
||||||
|
|
||||||
interface IPermission {
|
|
||||||
_id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let loading = true;
|
|
||||||
let appName: string;
|
|
||||||
let permissions: IPermission[] = [];
|
|
||||||
let accept: () => void;
|
|
||||||
|
|
||||||
const app = new App({
|
|
||||||
target: document.body,
|
|
||||||
props: { loading, accept },
|
|
||||||
});
|
|
||||||
|
|
||||||
const setLoading = (_loading: boolean) => {
|
|
||||||
loading = _loading;
|
|
||||||
app.$set({ loading });
|
|
||||||
};
|
|
||||||
|
|
||||||
const setAppName = (_appName: string) => {
|
|
||||||
appName = _appName;
|
|
||||||
app.$set({ appName });
|
|
||||||
};
|
|
||||||
|
|
||||||
const setPermissions = (_permissions: IPermission[]) => {
|
|
||||||
permissions = _permissions;
|
|
||||||
app.$set({ permissions });
|
|
||||||
};
|
|
||||||
|
|
||||||
const setAccept = (_accept: () => void) => {
|
|
||||||
accept = _accept;
|
|
||||||
app.$set({ accept });
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getJWT(client_id: string, origin: string) {
|
|
||||||
origin = encodeURIComponent(origin);
|
|
||||||
client_id = encodeURIComponent(client_id);
|
|
||||||
|
|
||||||
const res = await request(`/api/user/oauth/jwt`, {
|
|
||||||
client_id,
|
|
||||||
origin,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getRefreshToken(
|
|
||||||
client_id: string,
|
|
||||||
origin: string,
|
|
||||||
permissions: string[]
|
|
||||||
) {
|
|
||||||
origin = encodeURIComponent(origin);
|
|
||||||
client_id = encodeURIComponent(client_id);
|
|
||||||
const perm = permissions.map((e) => encodeURIComponent(e)).join(",");
|
|
||||||
|
|
||||||
const res = await request(`/api/user/oauth/refresh_token`, {
|
|
||||||
client_id,
|
|
||||||
origin,
|
|
||||||
permissions: perm,
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
let started = false;
|
|
||||||
async function onMessage(msg: MessageEvent<any>) {
|
|
||||||
const sendResponse = (data: any) => {
|
|
||||||
try {
|
|
||||||
console.log("Sending response:", data);
|
|
||||||
(msg.source.postMessage as any)(data, msg.origin);
|
|
||||||
} catch (err) {
|
|
||||||
alert("Something went wrong, please try again later!");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
console.log("Received message", msg, started);
|
|
||||||
if (!started) {
|
|
||||||
started = true;
|
|
||||||
const url = new URL(msg.origin);
|
|
||||||
setAppName(url.hostname);
|
|
||||||
|
|
||||||
if (!msg.data.client_id) {
|
|
||||||
alert("The site requesting the login is not valid");
|
|
||||||
window.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!msg.data.type || msg.data.type === "jwt") {
|
|
||||||
console.log("JWT Request");
|
|
||||||
|
|
||||||
await request(
|
|
||||||
"/api/user/oauth/permissions",
|
|
||||||
{
|
|
||||||
client_id: msg.data.client_id,
|
|
||||||
origin: url.hostname,
|
|
||||||
permissions: permissions.join(","),
|
|
||||||
}
|
|
||||||
); // Will fail if client does not exist
|
|
||||||
|
|
||||||
await new Promise<void>((yes) => {
|
|
||||||
console.log("Await user acceptance");
|
|
||||||
setLoading(false);
|
|
||||||
setAccept(yes);
|
|
||||||
});
|
|
||||||
console.log("User has accepted");
|
|
||||||
const res = await getJWT(msg.data.client_id, url.hostname);
|
|
||||||
sendResponse(res);
|
|
||||||
} else if (msg.data.type === "refresh") {
|
|
||||||
console.log("RefreshToken Request");
|
|
||||||
let permissions = msg.data.permissions || [];
|
|
||||||
let permissions_resolved = [];
|
|
||||||
|
|
||||||
if (permissions.length > 0) {
|
|
||||||
permissions_resolved = await request(
|
|
||||||
"/api/user/oauth/permissions",
|
|
||||||
{
|
|
||||||
client_id: msg.data.client_id,
|
|
||||||
origin: url.hostname,
|
|
||||||
permissions: permissions.join(","),
|
|
||||||
}
|
|
||||||
).then(({ permissions }) => permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((yes) => {
|
|
||||||
console.log("Await user acceptance");
|
|
||||||
setLoading(false);
|
|
||||||
setPermissions(permissions_resolved);
|
|
||||||
setAccept(yes);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("User has accepted");
|
|
||||||
|
|
||||||
const res = await getRefreshToken(
|
|
||||||
msg.data.client_id,
|
|
||||||
url.hostname,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
sendResponse(res);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
sendResponse({ error: true, message: err.message });
|
|
||||||
}
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!started) {
|
|
||||||
console.log("No authentication request received!");
|
|
||||||
alert(
|
|
||||||
"The site requesting the login does not respond. Please try again later"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
window.addEventListener("message", onMessage);
|
|
@ -1,42 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import MainNavbar from "../../components/MainNavbar.svelte";
|
|
||||||
import Sidebar from "./Sidebar.svelte";
|
|
||||||
import { CurrentPage } from "./nav";
|
|
||||||
import PersonalInfo from "./pages/PersonalInfo.svelte";
|
|
||||||
import Security from "./pages/Security.svelte";
|
|
||||||
|
|
||||||
let sidebarOpen = false;
|
|
||||||
let sidebarOpenVisible = false;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const unsub = CurrentPage.subscribe(() => {
|
|
||||||
sidebarOpen = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsub;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="grid main-grid min-h-screen overflow-hidden">
|
|
||||||
<div class="col-span-2">
|
|
||||||
<MainNavbar bind:sidebarOpen bind:sidebarOpenVisible />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Sidebar bind:sidebarOpen bind:sidebarOpenVisible />
|
|
||||||
</div>
|
|
||||||
<div class="overflow-auto p-4">
|
|
||||||
{#if $CurrentPage == "personal-info"}
|
|
||||||
<PersonalInfo />
|
|
||||||
{:else if $CurrentPage == "security"}
|
|
||||||
<Security />
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.main-grid {
|
|
||||||
grid-template-columns: auto 1fr;
|
|
||||||
grid-template-rows: auto 1fr;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Alert, Spinner } from "flowbite-svelte";
|
|
||||||
|
|
||||||
export let loading: boolean;
|
|
||||||
export let error: string | undefined;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if loading}
|
|
||||||
<div class="h-full flex justify-center items-center">
|
|
||||||
<Spinner size={"16"} />
|
|
||||||
</div>
|
|
||||||
{:else if error}
|
|
||||||
<Alert color="red">
|
|
||||||
<span class="font-medium">Error occured!</span>
|
|
||||||
{error}
|
|
||||||
</Alert>
|
|
||||||
{:else}
|
|
||||||
<slot />
|
|
||||||
{/if}
|
|
@ -1,54 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
Sidebar,
|
|
||||||
SidebarGroup,
|
|
||||||
SidebarItem,
|
|
||||||
SidebarWrapper,
|
|
||||||
} from "flowbite-svelte";
|
|
||||||
import { CurrentPage } from "./nav";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
export let sidebarOpen = false;
|
|
||||||
export let sidebarOpenVisible = false;
|
|
||||||
|
|
||||||
$: open = !sidebarOpenVisible || sidebarOpen;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
const mq = window.matchMedia("(max-width: 768px)");
|
|
||||||
const onChange = (e: MediaQueryListEvent) => {
|
|
||||||
sidebarOpenVisible = e.matches;
|
|
||||||
};
|
|
||||||
mq.addEventListener("change", onChange);
|
|
||||||
|
|
||||||
onChange({ matches: mq.matches } as MediaQueryListEvent);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
mq.removeEventListener("change", onChange);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Sidebar class="h-screen" style={open ? "display: block" : "display: none"}>
|
|
||||||
<SidebarWrapper class="h-full">
|
|
||||||
<SidebarGroup>
|
|
||||||
<SidebarItem
|
|
||||||
label="Personal Data"
|
|
||||||
active={$CurrentPage == "personal-info"}
|
|
||||||
href="#personal-info"
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="icon">
|
|
||||||
<span class="material-icons-outlined"> account_circle </span>
|
|
||||||
</svelte:fragment>
|
|
||||||
</SidebarItem>
|
|
||||||
<SidebarItem
|
|
||||||
label="Security"
|
|
||||||
active={$CurrentPage == "security"}
|
|
||||||
href="#security"
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="icon">
|
|
||||||
<span class="material-icons-outlined"> lock </span>
|
|
||||||
</svelte:fragment>
|
|
||||||
</SidebarItem>
|
|
||||||
</SidebarGroup>
|
|
||||||
</SidebarWrapper>
|
|
||||||
</Sidebar>
|
|
@ -1,6 +0,0 @@
|
|||||||
import "../../main.css";
|
|
||||||
import App from "./App.svelte";
|
|
||||||
|
|
||||||
new App({
|
|
||||||
target: document.body,
|
|
||||||
});
|
|
@ -1,25 +0,0 @@
|
|||||||
import { writable } from "svelte/store";
|
|
||||||
|
|
||||||
type Pages = "personal-info" | "security";
|
|
||||||
|
|
||||||
|
|
||||||
function getCurrentPage(): Pages | undefined {
|
|
||||||
let hash = window.location.hash;
|
|
||||||
if (hash.length > 0) {
|
|
||||||
hash = hash.substring(1);
|
|
||||||
if (hash === "personal-info" || hash === "security") {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CurrentPage = writable<Pages>(getCurrentPage() ?? "personal-info");
|
|
||||||
|
|
||||||
window.addEventListener("hashchange", () => {
|
|
||||||
CurrentPage.set(getCurrentPage() ?? "personal-info");
|
|
||||||
});
|
|
||||||
|
|
||||||
export function navigateTo(page: Pages) {
|
|
||||||
window.location.hash = "#" + page;
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Listgroup, ListgroupItem, Modal, Radio } from "flowbite-svelte";
|
|
||||||
import Totp from "./TwoFactorRegistration/TOTP.svelte";
|
|
||||||
import WebAuthn from "./TwoFactorRegistration/WebAuthn.svelte";
|
|
||||||
|
|
||||||
export let open: boolean;
|
|
||||||
|
|
||||||
let selectedType = undefined;
|
|
||||||
$: {
|
|
||||||
if (!open) {
|
|
||||||
selectedType = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Modal bind:open size="md" autoclose={false} class="w-full">
|
|
||||||
{#if !selectedType}
|
|
||||||
<h3 class="text-xl font-medium text-gray-900 dark:text-white p-0">
|
|
||||||
Select type
|
|
||||||
</h3>
|
|
||||||
<Listgroup active class="w-full">
|
|
||||||
<ListgroupItem
|
|
||||||
class="gap-2 px-4 py-4"
|
|
||||||
on:click={() => (selectedType = "totp")}>TOTP</ListgroupItem
|
|
||||||
>
|
|
||||||
<ListgroupItem
|
|
||||||
class="gap-2 px-4 py-4"
|
|
||||||
on:click={() => (selectedType = "webauthn")}>WebAuthn</ListgroupItem
|
|
||||||
>
|
|
||||||
</Listgroup>
|
|
||||||
{:else if selectedType == "totp"}
|
|
||||||
<Totp on:reload />
|
|
||||||
{:else if selectedType == "webauthn"}
|
|
||||||
<WebAuthn on:reload />
|
|
||||||
{/if}
|
|
||||||
</Modal>
|
|
@ -1,203 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
type ContactInfo,
|
|
||||||
type Profile,
|
|
||||||
Gender,
|
|
||||||
} from "@hibas123/openauth-internalapi";
|
|
||||||
import InternalAPI from "../../../helper/api";
|
|
||||||
import Loading from "../Loading.svelte";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Input,
|
|
||||||
Label,
|
|
||||||
Select,
|
|
||||||
Heading,
|
|
||||||
Spinner,
|
|
||||||
Helper,
|
|
||||||
} from "flowbite-svelte";
|
|
||||||
|
|
||||||
let profileInfo: Profile;
|
|
||||||
let loadedProfileInfo: Profile;
|
|
||||||
let contactInfo: ContactInfo;
|
|
||||||
|
|
||||||
let loading = true;
|
|
||||||
let error: string | undefined;
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
error = undefined;
|
|
||||||
loading = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
profileInfo = await InternalAPI.Account.GetProfile();
|
|
||||||
loadedProfileInfo = { ...profileInfo };
|
|
||||||
contactInfo = await InternalAPI.Account.GetContactInfos();
|
|
||||||
} catch (e) {
|
|
||||||
error = e.message;
|
|
||||||
} finally {
|
|
||||||
loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let savingProfile = false;
|
|
||||||
|
|
||||||
async function saveProfileChanges() {
|
|
||||||
savingProfile = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await new Promise((yes) => setTimeout(yes, 1000));
|
|
||||||
await InternalAPI.Account.UpdateProfile(profileInfo);
|
|
||||||
loadedProfileInfo = { ...profileInfo };
|
|
||||||
} catch (e) {
|
|
||||||
error = e.message;
|
|
||||||
} finally {
|
|
||||||
savingProfile = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: hasProfileChanged =
|
|
||||||
JSON.stringify(profileInfo) != JSON.stringify(loadedProfileInfo);
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
load();
|
|
||||||
});
|
|
||||||
|
|
||||||
let genders = [
|
|
||||||
{
|
|
||||||
value: Gender.None,
|
|
||||||
name: "Not saying",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: Gender.Male,
|
|
||||||
name: "Male",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: Gender.Female,
|
|
||||||
name: "Female",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: Gender.Other,
|
|
||||||
name: "Other",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Loading {loading} {error}>
|
|
||||||
<div class="flex flex-wrap gap-4">
|
|
||||||
<Card size="md" class="w-full">
|
|
||||||
<Heading tag="h5">General Account Details</Heading>
|
|
||||||
<hr class="mb-6" />
|
|
||||||
<div class="mb-6">
|
|
||||||
<Label for="name-input" class="block mb-2">Name</Label>
|
|
||||||
<Input
|
|
||||||
id="name-input"
|
|
||||||
placeholder="Name"
|
|
||||||
bind:value={profileInfo.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-6">
|
|
||||||
<Label for="birthday-input" class="block mb-2">Birthday (WIP)</Label>
|
|
||||||
<Input
|
|
||||||
id="birthday-input"
|
|
||||||
placeholder="Birthday"
|
|
||||||
disabled
|
|
||||||
bind:value={profileInfo.birthday}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-6">
|
|
||||||
<Label class="block mb-2"
|
|
||||||
>Gender
|
|
||||||
<Select items={genders} bind:value={profileInfo.gender} />
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
disabled={!hasProfileChanged || savingProfile}
|
|
||||||
on:click={saveProfileChanges}
|
|
||||||
>
|
|
||||||
{#if savingProfile}
|
|
||||||
<Spinner class="mr-3" size="4" color="white" /> Saving...
|
|
||||||
{:else}
|
|
||||||
Save
|
|
||||||
{/if}
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card size="md" class="w-full">
|
|
||||||
<Heading tag="h5">Contact Details (WIP)</Heading>
|
|
||||||
<hr class="mb-6" />
|
|
||||||
|
|
||||||
<Heading tag="h6" color="gray">Mails</Heading>
|
|
||||||
<hr class="mb-6" />
|
|
||||||
|
|
||||||
{#each contactInfo.mail as mail}
|
|
||||||
<div class="mb-6">
|
|
||||||
<!-- <Label for="mail-input" class="block mb-2">Mail</Label> -->
|
|
||||||
<Input
|
|
||||||
id="mail-input"
|
|
||||||
placeholder="Mail"
|
|
||||||
bind:value={mail.mail}
|
|
||||||
color={mail.verified ? "green" : "base"}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
{#if mail.verified}
|
|
||||||
<Helper class="mt-2" color="green"
|
|
||||||
><span class="font-medium">Well done!</span> E-Mail is verified.</Helper
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<Helper class="mt-2" color="gray"
|
|
||||||
><span class="font-medium">Oh no!</span> E-Mail needs verification.</Helper
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<Heading tag="h6" color="gray">Phones</Heading>
|
|
||||||
<hr class="mb-6" />
|
|
||||||
|
|
||||||
{#each contactInfo.phone as phone}
|
|
||||||
<div class="mb-6">
|
|
||||||
<!-- <Label for="phone-input" class="block mb-2">Phone</Label> -->
|
|
||||||
<Input
|
|
||||||
id="phone-input"
|
|
||||||
placeholder="Phone"
|
|
||||||
bind:value={phone.phone}
|
|
||||||
color={phone.verified ? "green" : "base"}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
{#if phone.verified}
|
|
||||||
<Helper class="mt-2" color="green"
|
|
||||||
><span class="font-medium">Well done!</span> Phone is verified.</Helper
|
|
||||||
>
|
|
||||||
{:else}
|
|
||||||
<Helper class="mt-2" color="gray"
|
|
||||||
><span class="font-medium">Oh no!</span> Phone needs verification.</Helper
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
<!-- <div class="mb-6">
|
|
||||||
<Label for="name-input" class="block mb-2">Name</Label>
|
|
||||||
<Input id="name-input" placeholder="Name" bind:value={profileInfo.name} />
|
|
||||||
</div>
|
|
||||||
<div class="mb-6">
|
|
||||||
<Label for="birthday-input" class="block mb-2">Birthday (WIP)</Label>
|
|
||||||
<Input
|
|
||||||
id="birthday-input"
|
|
||||||
placeholder="Birthday"
|
|
||||||
disabled
|
|
||||||
bind:value={profileInfo.birthday}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-6">
|
|
||||||
<Label class="block mb-2"
|
|
||||||
>Gender
|
|
||||||
<Select items={genders} bind:value={profileInfo.gender} />
|
|
||||||
</Label>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<!-- <Button>Save</Button> -->
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</Loading>
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user