Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad3e51a9e8 | |||
| d8f72d620a | |||
| 53b36e506c | |||
| 7d5ad29a27 | |||
| 724ec2d134 | |||
| 32ffc1bbaa |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 135 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-01-29 - 1.16.0 - feat(dev)
|
||||||
|
add local development docs, update tswatch preset and add Playwright screenshots
|
||||||
|
|
||||||
|
- readme.md: added a Local Development section with prerequisites, quick-start commands, environment variables, development routes, and default development credentials + security note
|
||||||
|
- npmextra.json: changed @git.zone/tswatch preset from "website" to "service" and disabled the built-in server (removed port/serveDir/liveReload and set server.enabled false); removed triggerReload from website watcher
|
||||||
|
- .playwright-mcp: added Playwright screenshots (login-page.png, register-page.png, account-dashboard.png) for visual tests / CI
|
||||||
|
|
||||||
|
## 2026-01-29 - 1.15.0 - feat(build)
|
||||||
|
add tsbundle/tswatch configs, update build/watch scripts, bump dependencies, and add CLI documentation
|
||||||
|
|
||||||
|
- Add tsbundle and tswatch configuration to npmextra.json to support bundling and a local dev server (dist_serve, liveReload, watch patterns).
|
||||||
|
- Update package.json build/watch scripts to use generic tsbundle/tswatch invocations (removed explicit 'website' target).
|
||||||
|
- Bump dependencies and devDependencies: @git.zone/tsbuild ^4.0.2 -> ^4.1.2, @git.zone/tsbundle ^2.6.3 -> ^2.8.3, @git.zone/tswatch ^2.3.13 -> ^3.0.1, @api.global/typedserver ^8.1.0 -> ^8.3.0, several @design.estate packages, @push.rocks/taskbuffer ^3.5.0 -> ^4.1.1, @types/node 25.0.3 -> 25.1.0, and other minor/patch bumps.
|
||||||
|
- Add a new CLI README (ts_idpcli/readme.md) with usage, commands, programmatic API examples and configuration.
|
||||||
|
- Update README license/Legal sections in ts_idpclient, ts_interfaces and ts_web to include license, trademark, and company information.
|
||||||
|
|
||||||
|
## 2025-12-22 - 1.14.1 - fix(oidc)
|
||||||
|
migrate OIDC endpoints and internal handlers to use typedserver IRequestContext and update dependencies
|
||||||
|
|
||||||
|
- Updated route handlers in ts/index.ts to pass ctx (IRequestContext) instead of req
|
||||||
|
- Refactored OIDC manager handlers to accept plugins.typedserver.IRequestContext and use ctx.url, ctx.headers, ctx.formData (handleAuthorize, handleToken, handleUserInfo, handleRevoke)
|
||||||
|
- Bumped dependencies to support the new typedserver API: @api.global/typedserver -> ^8.1.0
|
||||||
|
- Other dependency updates: @design.estate/dees-catalog ^3.4.0, @git.zone/tspublish ^1.11.0, @types/node ^25.0.3
|
||||||
|
- Changing public handler method signatures is a breaking API change; recommend a major version bump
|
||||||
|
|
||||||
## 2025-12-16 - 1.14.0 - feat(docs)
|
## 2025-12-16 - 1.14.0 - feat(docs)
|
||||||
add package READMEs and publish metadata; update web package publish order
|
add package READMEs and publish metadata; update web package publish order
|
||||||
|
|
||||||
|
|||||||
@@ -50,5 +50,40 @@
|
|||||||
"registries": ["https://verdaccio.lossless.digital"],
|
"registries": ["https://verdaccio.lossless.digital"],
|
||||||
"accessLevel": "public"
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"@git.zone/tsbundle": {
|
||||||
|
"bundles": [
|
||||||
|
{
|
||||||
|
"from": "./ts_web/index.ts",
|
||||||
|
"to": "./dist_serve/bundle.js",
|
||||||
|
"outputMode": "bundle",
|
||||||
|
"bundler": "esbuild",
|
||||||
|
"production": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@git.zone/tswatch": {
|
||||||
|
"preset": "service",
|
||||||
|
"server": {
|
||||||
|
"enabled": false
|
||||||
|
},
|
||||||
|
"watchers": [
|
||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"watch": "./ts/**/*",
|
||||||
|
"command": "npm run startTs",
|
||||||
|
"restart": true,
|
||||||
|
"debounce": 300,
|
||||||
|
"runOnStart": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bundles": [
|
||||||
|
{
|
||||||
|
"name": "website",
|
||||||
|
"from": "./ts_web/index.ts",
|
||||||
|
"to": "./dist_serve/bundle.js",
|
||||||
|
"watchPatterns": ["./ts_web/**/*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-14
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@idp.global/idp.global",
|
"name": "@idp.global/idp.global",
|
||||||
"version": "1.14.0",
|
"version": "1.16.0",
|
||||||
"description": "An identity provider software managing user authentications, registrations, and sessions.",
|
"description": "An identity provider software managing user authentications, registrations, and sessions.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run build",
|
"test": "npm run build",
|
||||||
"build": "tsbuild tsfolders --web --allowimplicitany && tsbundle website --production",
|
"build": "tsbuild tsfolders --web --allowimplicitany && tsbundle",
|
||||||
"watch": "tswatch website",
|
"watch": "tswatch",
|
||||||
"start": "(node cli.js)",
|
"start": "(node cli.js)",
|
||||||
"startTs": "(node cli.ts.js)",
|
"startTs": "(node cli.ts.js)",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
@@ -18,16 +18,16 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest": "^3.2.5",
|
"@api.global/typedrequest": "^3.2.5",
|
||||||
"@api.global/typedrequest-interfaces": "^3.0.19",
|
"@api.global/typedrequest-interfaces": "^3.0.19",
|
||||||
"@api.global/typedserver": "^7.11.1",
|
"@api.global/typedserver": "^8.3.0",
|
||||||
"@api.global/typedsocket": "^4.1.0",
|
"@api.global/typedsocket": "^4.1.0",
|
||||||
"@consent.software/catalog": "^2.0.1",
|
"@consent.software/catalog": "^2.0.1",
|
||||||
"@design.estate/dees-catalog": "^3.3.1",
|
"@design.estate/dees-catalog": "^3.41.4",
|
||||||
"@design.estate/dees-domtools": "^2.3.6",
|
"@design.estate/dees-domtools": "^2.3.8",
|
||||||
"@design.estate/dees-element": "^2.1.3",
|
"@design.estate/dees-element": "^2.1.6",
|
||||||
"@git.zone/tspublish": "^1.10.3",
|
"@git.zone/tspublish": "^1.11.0",
|
||||||
"@push.rocks/lik": "^6.2.2",
|
"@push.rocks/lik": "^6.2.2",
|
||||||
"@push.rocks/qenv": "^6.1.3",
|
"@push.rocks/qenv": "^6.1.3",
|
||||||
"@push.rocks/smartcli": "^4.0.19",
|
"@push.rocks/smartcli": "^4.0.20",
|
||||||
"@push.rocks/smartdata": "^7.0.15",
|
"@push.rocks/smartdata": "^7.0.15",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartfile": "^13.1.0",
|
"@push.rocks/smartfile": "^13.1.0",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"@push.rocks/smarttime": "^4.1.1",
|
"@push.rocks/smarttime": "^4.1.1",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@push.rocks/smarturl": "^3.1.0",
|
"@push.rocks/smarturl": "^3.1.0",
|
||||||
"@push.rocks/taskbuffer": "^3.5.0",
|
"@push.rocks/taskbuffer": "^4.1.1",
|
||||||
"@push.rocks/webjwt": "^1.0.9",
|
"@push.rocks/webjwt": "^1.0.9",
|
||||||
"@push.rocks/websetup": "^3.0.15",
|
"@push.rocks/websetup": "^3.0.15",
|
||||||
"@push.rocks/webstore": "^2.0.20",
|
"@push.rocks/webstore": "^2.0.20",
|
||||||
@@ -53,12 +53,12 @@
|
|||||||
"@uptime.link/webwidget": "^1.2.6"
|
"@uptime.link/webwidget": "^1.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^4.0.2",
|
"@git.zone/tsbuild": "^4.1.2",
|
||||||
"@git.zone/tsbundle": "^2.6.3",
|
"@git.zone/tsbundle": "^2.8.3",
|
||||||
"@git.zone/tsrun": "^2.0.1",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tswatch": "^2.3.13",
|
"@git.zone/tswatch": "^3.0.1",
|
||||||
"@push.rocks/projectinfo": "^5.0.1",
|
"@push.rocks/projectinfo": "^5.0.1",
|
||||||
"@types/node": "^24.10.1"
|
"@types/node": "^25.1.0"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
Generated
+869
-766
File diff suppressed because it is too large
Load Diff
@@ -130,6 +130,68 @@ volumes:
|
|||||||
|
|
||||||
The server listens on port 2999 by default.
|
The server listens on port 2999 by default.
|
||||||
|
|
||||||
|
## 🛠️ Local Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 20+
|
||||||
|
- pnpm
|
||||||
|
- MongoDB (local or remote)
|
||||||
|
- SMTP server (for email verification in registration flow)
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://code.foss.global/idp.global/idp.global.git
|
||||||
|
cd idp.global
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# Start development server with hot reload
|
||||||
|
pnpm watch
|
||||||
|
```
|
||||||
|
|
||||||
|
The server runs on **http://localhost:2999** with:
|
||||||
|
- 🔄 Auto-restart backend on changes (`ts/`)
|
||||||
|
- 📦 Automatic frontend bundle rebuilding (`ts_web/`)
|
||||||
|
|
||||||
|
### Environment Setup
|
||||||
|
|
||||||
|
Create environment variables for the backend:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MONGODB_URL=mongodb://localhost:27017/idp-dev
|
||||||
|
export IDP_BASEURL=http://localhost:2999
|
||||||
|
export INSTANCE_NAME=idp-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Routes
|
||||||
|
|
||||||
|
| Route | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `/` | Welcome/landing page |
|
||||||
|
| `/login` | Sign in form |
|
||||||
|
| `/register` | New user registration |
|
||||||
|
| `/account` | User dashboard (requires auth) |
|
||||||
|
|
||||||
|
### 🔑 Default Development Credentials
|
||||||
|
|
||||||
|
For local development with the test database, use:
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| **Email/Username** | `admin@idp.global` or `admin` |
|
||||||
|
| **Password** | `admin` |
|
||||||
|
|
||||||
|
This account has `isGlobalAdmin: true` for full platform access including the admin panel at `/account/admin`.
|
||||||
|
|
||||||
|
> ⚠️ **Security Note**: These credentials are for local development only. Never use default credentials in production environments.
|
||||||
|
|
||||||
## 📦 Published Packages
|
## 📦 Published Packages
|
||||||
|
|
||||||
This monorepo publishes the following npm packages:
|
This monorepo publishes the following npm packages:
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@idp.global/idp.global',
|
name: '@idp.global/idp.global',
|
||||||
version: '1.14.0',
|
version: '1.16.0',
|
||||||
description: 'An identity provider software managing user authentications, registrations, and sessions.'
|
description: 'An identity provider software managing user authentications, registrations, and sessions.'
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -28,40 +28,40 @@ export const runCli = async () => {
|
|||||||
typedserver.options.spaFallback = true;
|
typedserver.options.spaFallback = true;
|
||||||
|
|
||||||
// OIDC Discovery endpoint
|
// OIDC Discovery endpoint
|
||||||
typedserver.addRoute('/.well-known/openid-configuration', 'GET', async (req) => {
|
typedserver.addRoute('/.well-known/openid-configuration', 'GET', async (ctx) => {
|
||||||
return new Response(JSON.stringify(reception.oidcManager.getDiscoveryDocument()), {
|
return new Response(JSON.stringify(reception.oidcManager.getDiscoveryDocument()), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// JWKS endpoint
|
// JWKS endpoint
|
||||||
typedserver.addRoute('/.well-known/jwks.json', 'GET', async (req) => {
|
typedserver.addRoute('/.well-known/jwks.json', 'GET', async (ctx) => {
|
||||||
return new Response(JSON.stringify(reception.oidcManager.getJwks()), {
|
return new Response(JSON.stringify(reception.oidcManager.getJwks()), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// OAuth Authorization endpoint
|
// OAuth Authorization endpoint
|
||||||
typedserver.addRoute('/oauth/authorize', 'GET', async (req) => {
|
typedserver.addRoute('/oauth/authorize', 'GET', async (ctx) => {
|
||||||
return reception.oidcManager.handleAuthorize(req);
|
return reception.oidcManager.handleAuthorize(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// OAuth Token endpoint
|
// OAuth Token endpoint
|
||||||
typedserver.addRoute('/oauth/token', 'POST', async (req) => {
|
typedserver.addRoute('/oauth/token', 'POST', async (ctx) => {
|
||||||
return reception.oidcManager.handleToken(req);
|
return reception.oidcManager.handleToken(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// OAuth UserInfo endpoint (GET and POST)
|
// OAuth UserInfo endpoint (GET and POST)
|
||||||
typedserver.addRoute('/oauth/userinfo', 'GET', async (req) => {
|
typedserver.addRoute('/oauth/userinfo', 'GET', async (ctx) => {
|
||||||
return reception.oidcManager.handleUserInfo(req);
|
return reception.oidcManager.handleUserInfo(ctx);
|
||||||
});
|
});
|
||||||
typedserver.addRoute('/oauth/userinfo', 'POST', async (req) => {
|
typedserver.addRoute('/oauth/userinfo', 'POST', async (ctx) => {
|
||||||
return reception.oidcManager.handleUserInfo(req);
|
return reception.oidcManager.handleUserInfo(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// OAuth Revocation endpoint
|
// OAuth Revocation endpoint
|
||||||
typedserver.addRoute('/oauth/revoke', 'POST', async (req) => {
|
typedserver.addRoute('/oauth/revoke', 'POST', async (ctx) => {
|
||||||
return reception.oidcManager.handleRevoke(req);
|
return reception.oidcManager.handleRevoke(ctx);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,9 +95,8 @@ export class OidcManager {
|
|||||||
/**
|
/**
|
||||||
* Handle the authorization endpoint request
|
* Handle the authorization endpoint request
|
||||||
*/
|
*/
|
||||||
public async handleAuthorize(request: Request): Promise<Response> {
|
public async handleAuthorize(ctx: plugins.typedserver.IRequestContext): Promise<Response> {
|
||||||
const url = new URL(request.url);
|
const params = ctx.url.searchParams;
|
||||||
const params = url.searchParams;
|
|
||||||
|
|
||||||
// Extract authorization request parameters
|
// Extract authorization request parameters
|
||||||
const clientId = params.get('client_id');
|
const clientId = params.get('client_id');
|
||||||
@@ -196,21 +195,21 @@ export class OidcManager {
|
|||||||
/**
|
/**
|
||||||
* Handle the token endpoint request
|
* Handle the token endpoint request
|
||||||
*/
|
*/
|
||||||
public async handleToken(request: Request): Promise<Response> {
|
public async handleToken(ctx: plugins.typedserver.IRequestContext): Promise<Response> {
|
||||||
// Parse form data
|
// Parse form data
|
||||||
const contentType = request.headers.get('content-type');
|
const contentType = ctx.headers.get('content-type');
|
||||||
if (!contentType?.includes('application/x-www-form-urlencoded')) {
|
if (!contentType?.includes('application/x-www-form-urlencoded')) {
|
||||||
return this.tokenErrorResponse('invalid_request', 'Content-Type must be application/x-www-form-urlencoded');
|
return this.tokenErrorResponse('invalid_request', 'Content-Type must be application/x-www-form-urlencoded');
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = await request.formData();
|
const formData = await ctx.formData();
|
||||||
const grantType = formData.get('grant_type') as string;
|
const grantType = formData.get('grant_type') as string;
|
||||||
|
|
||||||
// Extract client credentials from Basic auth or form
|
// Extract client credentials from Basic auth or form
|
||||||
let clientId = formData.get('client_id') as string;
|
let clientId = formData.get('client_id') as string;
|
||||||
let clientSecret = formData.get('client_secret') as string;
|
let clientSecret = formData.get('client_secret') as string;
|
||||||
|
|
||||||
const authHeader = request.headers.get('authorization');
|
const authHeader = ctx.headers.get('authorization');
|
||||||
if (authHeader?.startsWith('Basic ')) {
|
if (authHeader?.startsWith('Basic ')) {
|
||||||
const base64 = authHeader.substring(6);
|
const base64 = authHeader.substring(6);
|
||||||
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
|
const decoded = Buffer.from(base64, 'base64').toString('utf-8');
|
||||||
@@ -469,9 +468,9 @@ export class OidcManager {
|
|||||||
/**
|
/**
|
||||||
* Handle the userinfo endpoint
|
* Handle the userinfo endpoint
|
||||||
*/
|
*/
|
||||||
public async handleUserInfo(request: Request): Promise<Response> {
|
public async handleUserInfo(ctx: plugins.typedserver.IRequestContext): Promise<Response> {
|
||||||
// Get access token from Authorization header
|
// Get access token from Authorization header
|
||||||
const authHeader = request.headers.get('authorization');
|
const authHeader = ctx.headers.get('authorization');
|
||||||
if (!authHeader?.startsWith('Bearer ')) {
|
if (!authHeader?.startsWith('Bearer ')) {
|
||||||
return new Response(JSON.stringify({ error: 'invalid_token' }), {
|
return new Response(JSON.stringify({ error: 'invalid_token' }), {
|
||||||
status: 401,
|
status: 401,
|
||||||
@@ -575,8 +574,8 @@ export class OidcManager {
|
|||||||
/**
|
/**
|
||||||
* Handle the revocation endpoint
|
* Handle the revocation endpoint
|
||||||
*/
|
*/
|
||||||
public async handleRevoke(request: Request): Promise<Response> {
|
public async handleRevoke(ctx: plugins.typedserver.IRequestContext): Promise<Response> {
|
||||||
const formData = await request.formData();
|
const formData = await ctx.formData();
|
||||||
const token = formData.get('token') as string;
|
const token = formData.get('token') as string;
|
||||||
const tokenTypeHint = formData.get('token_type_hint') as string;
|
const tokenTypeHint = formData.get('token_type_hint') as string;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
# @idp.global/cli
|
||||||
|
|
||||||
|
Command-line interface for interacting with the idp.global Identity Provider. A Node.js CLI tool that provides authentication, user management, and organization administration from the terminal.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The IdpCli module provides a complete command-line interface for managing your idp.global account and organizations. It uses file-based credential storage and WebSocket connections for real-time communication with the IdP server.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @idp.global/cli
|
||||||
|
# or
|
||||||
|
pnpm add -g @idp.global/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login with email and password
|
||||||
|
idp login
|
||||||
|
|
||||||
|
# Check current user
|
||||||
|
idp whoami
|
||||||
|
|
||||||
|
# List your organizations
|
||||||
|
idp orgs
|
||||||
|
|
||||||
|
# Logout
|
||||||
|
idp logout
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `idp login` | Interactive login with email and password |
|
||||||
|
| `idp login-token` | Login with an API token |
|
||||||
|
| `idp logout` | Clear stored credentials and end session |
|
||||||
|
|
||||||
|
### User Information
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `idp whoami` | Display current user information |
|
||||||
|
| `idp sessions` | List all active sessions |
|
||||||
|
| `idp revoke --session <id>` | Revoke a specific session |
|
||||||
|
|
||||||
|
### Organization Management
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `idp orgs` | List all organizations you belong to |
|
||||||
|
| `idp orgs-create` | Create a new organization (interactive) |
|
||||||
|
| `idp members --org <id>` | List members of an organization |
|
||||||
|
| `idp invite --org <id> --email <email>` | Invite a user to an organization |
|
||||||
|
|
||||||
|
### Admin Commands (Global Admins Only)
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `idp admin-check` | Check if you are a global admin |
|
||||||
|
| `idp admin-apps` | List all global apps with connection stats |
|
||||||
|
| `idp admin-suspend --user <id>` | Suspend a user account |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `IDP_URL` | Override the IdP server URL | `https://idp.global` |
|
||||||
|
|
||||||
|
### Credential Storage
|
||||||
|
|
||||||
|
Credentials are stored in `~/.idp-global/credentials.json`. This file contains your refresh token and JWT for persistent authentication across CLI sessions.
|
||||||
|
|
||||||
|
## Programmatic Usage
|
||||||
|
|
||||||
|
You can also use the IdpCli class programmatically:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { IdpCli } from '@idp.global/cli';
|
||||||
|
|
||||||
|
const cli = new IdpCli({
|
||||||
|
idpBaseUrl: 'https://idp.global',
|
||||||
|
configDir: '/custom/config/path', // optional
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login
|
||||||
|
await cli.loginWithPassword('user@example.com', 'password');
|
||||||
|
|
||||||
|
// Get current user
|
||||||
|
const user = await cli.whoami();
|
||||||
|
console.log('Logged in as:', user.data.name);
|
||||||
|
|
||||||
|
// Get organizations
|
||||||
|
const { organizations, roles } = await cli.getOrganizations();
|
||||||
|
for (const org of organizations) {
|
||||||
|
console.log(`- ${org.data.name} (${org.id})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disconnect when done
|
||||||
|
await cli.disconnect();
|
||||||
|
```
|
||||||
|
|
||||||
|
### IdpCli Class Methods
|
||||||
|
|
||||||
|
**Authentication:**
|
||||||
|
- `loginWithPassword(email, password)` - Login with credentials
|
||||||
|
- `loginWithApiToken(token)` - Login with API token
|
||||||
|
- `refreshJwt()` - Refresh the current JWT
|
||||||
|
- `logout()` - Clear credentials and end session
|
||||||
|
|
||||||
|
**User:**
|
||||||
|
- `whoami()` - Get current user info
|
||||||
|
- `getSessions()` - Get active sessions
|
||||||
|
- `revokeSession(sessionId)` - Revoke a session
|
||||||
|
|
||||||
|
**Organizations:**
|
||||||
|
- `getOrganizations()` - List user's organizations
|
||||||
|
- `createOrganization(name, slug, mode)` - Create new organization
|
||||||
|
- `getOrgMembers(orgId)` - Get organization members
|
||||||
|
- `inviteMember(orgId, email, roles)` - Invite a user
|
||||||
|
|
||||||
|
**Admin:**
|
||||||
|
- `checkGlobalAdmin()` - Check admin status
|
||||||
|
- `getGlobalAppStats()` - Get app statistics
|
||||||
|
- `suspendUser(userId)` - Suspend a user
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Create an Organization
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ idp orgs-create
|
||||||
|
Organization Name: My Company
|
||||||
|
Organization Slug: my-company
|
||||||
|
|
||||||
|
Organization created successfully!
|
||||||
|
ID: org_abc123
|
||||||
|
Name: My Company
|
||||||
|
```
|
||||||
|
|
||||||
|
### Invite Team Members
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ idp invite --org org_abc123 --email colleague@example.com
|
||||||
|
Invitation sent to colleague@example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Active Sessions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ idp sessions
|
||||||
|
|
||||||
|
Active Sessions:
|
||||||
|
- sess_xyz789
|
||||||
|
Device: MacBook Pro
|
||||||
|
Browser: Chrome
|
||||||
|
OS: macOS
|
||||||
|
Last Active: 1/29/2025, 2:30:00 PM
|
||||||
|
Current: Yes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- `@api.global/typedrequest` - Type-safe API requests
|
||||||
|
- `@api.global/typedsocket` - WebSocket communication
|
||||||
|
- `@push.rocks/smartcli` - CLI framework
|
||||||
|
- `@push.rocks/smartinteract` - Interactive prompts
|
||||||
|
- `@idp.global/interfaces` - TypeScript interfaces
|
||||||
|
|
||||||
|
## License and Legal Information
|
||||||
|
|
||||||
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../LICENSE) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
+19
-2
@@ -367,6 +367,23 @@ Access via `idpClient.requests.*`:
|
|||||||
|
|
||||||
**Admin**: `checkGlobalAdmin`, `getGlobalAppStats`, `createGlobalApp`, `updateGlobalApp`, `deleteGlobalApp`, `suspendUser`, `deleteSuspendedUser`
|
**Admin**: `checkGlobalAdmin`, `getGlobalAppStats`, `createGlobalApp`, `updateGlobalApp`, `deleteGlobalApp`, `suspendUser`, `deleteSuspendedUser`
|
||||||
|
|
||||||
## License
|
## License and Legal Information
|
||||||
|
|
||||||
MIT - See the main repository for full license details.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../LICENSE) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
+19
-2
@@ -307,6 +307,23 @@ interface IReq_LoginWithEmailOrUsernameAndPassword {
|
|||||||
| `organizations` | User's organization memberships |
|
| `organizations` | User's organization memberships |
|
||||||
| `roles` | User's roles within organizations |
|
| `roles` | User's roles within organizations |
|
||||||
|
|
||||||
## License
|
## License and Legal Information
|
||||||
|
|
||||||
MIT - See the main repository for full license details.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../LICENSE) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@idp.global/idp.global',
|
name: '@idp.global/idp.global',
|
||||||
version: '1.14.0',
|
version: '1.16.0',
|
||||||
description: 'An identity provider software managing user authentications, registrations, and sessions.'
|
description: 'An identity provider software managing user authentications, registrations, and sessions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,19 @@ export const cardStyles = css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base styles for all view components
|
||||||
|
* Provides consistent background and foreground colors
|
||||||
|
*/
|
||||||
|
export const viewBaseStyles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
min-height: 100%;
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Typography styles for consistent text hierarchy
|
* Typography styles for consistent text hierarchy
|
||||||
*/
|
*/
|
||||||
@@ -108,10 +121,3 @@ export const navigationStyles = css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
|
||||||
* Legacy export for backwards compatibility
|
|
||||||
*/
|
|
||||||
export default css`
|
|
||||||
${accountDesignTokens}
|
|
||||||
${typographyStyles}
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import { IdpState } from '../../../states/idp.state.js';
|
import { IdpState } from '../../../states/idp.state.js';
|
||||||
import { accountDesignTokens } from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -43,15 +43,9 @@ export class AdminView extends DeesElement {
|
|||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
accountDesignTokens,
|
sharedStyles.accountDesignTokens,
|
||||||
|
sharedStyles.viewBaseStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
min-height: 100%;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
state,
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import sharedStyles, { accountDesignTokens, cardStyles, typographyStyles } from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
import * as accountState from '../../../states/accountstate.js';
|
import * as accountState from '../../../states/accountstate.js';
|
||||||
import { IdpState } from '../../../states/idp.state.js';
|
import { IdpState } from '../../../states/idp.state.js';
|
||||||
|
|
||||||
@@ -45,12 +45,12 @@ export class AppsView extends DeesElement {
|
|||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
accountDesignTokens,
|
sharedStyles.accountDesignTokens,
|
||||||
cardStyles,
|
sharedStyles.viewBaseStyles,
|
||||||
typographyStyles,
|
sharedStyles.cardStyles,
|
||||||
|
sharedStyles.typographyStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
|
||||||
padding: 48px;
|
padding: 48px;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import { accountDesignTokens } from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
import * as accountStateModule from '../../../states/accountstate.js';
|
import * as accountStateModule from '../../../states/accountstate.js';
|
||||||
import { IdpState } from '../../../states/idp.state.js';
|
import { IdpState } from '../../../states/idp.state.js';
|
||||||
|
|
||||||
@@ -59,15 +59,9 @@ export class BaseView extends DeesElement {
|
|||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
accountDesignTokens,
|
sharedStyles.accountDesignTokens,
|
||||||
|
sharedStyles.viewBaseStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
min-height: 100%;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import { accountDesignTokens } from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
import * as accountStateModule from '../../../states/accountstate.js';
|
import * as accountStateModule from '../../../states/accountstate.js';
|
||||||
import { IdpState } from '../../../states/idp.state.js';
|
import { IdpState } from '../../../states/idp.state.js';
|
||||||
|
|
||||||
@@ -41,14 +41,9 @@ export class OrgView extends DeesElement {
|
|||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
accountDesignTokens,
|
sharedStyles.accountDesignTokens,
|
||||||
|
sharedStyles.viewBaseStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
min-height: 100%;
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as plugins from '../../../plugins.js';
|
import * as plugins from '../../../plugins.js';
|
||||||
import sharedStyles from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
import * as state from '../../../states/accountstate.js';
|
import * as state from '../../../states/accountstate.js';
|
||||||
import { IdpState } from '../../../states/idp.state.js';
|
import { IdpState } from '../../../states/idp.state.js';
|
||||||
|
|
||||||
@@ -23,13 +23,13 @@ declare global {
|
|||||||
export class PaddleSetupView extends DeesElement {
|
export class PaddleSetupView extends DeesElement {
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
sharedStyles,
|
sharedStyles.accountDesignTokens,
|
||||||
|
sharedStyles.viewBaseStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
padding: 48px;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: auto;
|
margin: 0 auto;
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
css,
|
css,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import sharedStyles, { accountDesignTokens, cardStyles, typographyStyles } from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
|
|
||||||
import * as state from '../../../states/accountstate.js';
|
import * as state from '../../../states/accountstate.js';
|
||||||
|
|
||||||
@@ -46,12 +46,12 @@ export class SubscriptionView extends DeesElement {
|
|||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
accountDesignTokens,
|
sharedStyles.accountDesignTokens,
|
||||||
cardStyles,
|
sharedStyles.viewBaseStyles,
|
||||||
typographyStyles,
|
sharedStyles.cardStyles,
|
||||||
|
sharedStyles.typographyStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
|
||||||
padding: 48px;
|
padding: 48px;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import sharedStyles, { accountDesignTokens, cardStyles, typographyStyles } from '../sharedstyles.js';
|
import * as sharedStyles from '../sharedstyles.js';
|
||||||
import * as accountState from '../../../states/accountstate.js';
|
import * as accountState from '../../../states/accountstate.js';
|
||||||
import { IdpState } from '../../../states/idp.state.js';
|
import { IdpState } from '../../../states/idp.state.js';
|
||||||
import { BulkInviteModal } from '../bulk-invite-modal.js';
|
import { BulkInviteModal } from '../bulk-invite-modal.js';
|
||||||
@@ -83,12 +83,12 @@ export class UsersView extends DeesElement {
|
|||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
accountDesignTokens,
|
sharedStyles.accountDesignTokens,
|
||||||
cardStyles,
|
sharedStyles.viewBaseStyles,
|
||||||
typographyStyles,
|
sharedStyles.cardStyles,
|
||||||
|
sharedStyles.typographyStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
|
||||||
padding: 48px;
|
padding: 48px;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
+19
-2
@@ -251,6 +251,23 @@ pnpm build
|
|||||||
|
|
||||||
The bundled output is served from `dist_ts_web/` by the TypedServer.
|
The bundled output is served from `dist_ts_web/` by the TypedServer.
|
||||||
|
|
||||||
## License
|
## License and Legal Information
|
||||||
|
|
||||||
MIT - See the main repository for full license details.
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../LICENSE) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
|||||||
Reference in New Issue
Block a user