fix(docs): refresh module readmes and add repository license file
This commit is contained in:
+78
-311
@@ -1,71 +1,61 @@
|
||||
# @idp.global/idpclient
|
||||
# @idp.global/client
|
||||
|
||||
A TypeScript client library for integrating with the idp.global Identity Provider. Works in both browser and Node.js environments.
|
||||
Browser-facing TypeScript client for talking to an `idp.global` server over `typedrequest` and `typedsocket`.
|
||||
|
||||
## Overview
|
||||
It handles login state, refresh tokens, JWT housekeeping, cross-app transfer tokens, and direct access to the typed request surface.
|
||||
|
||||
The IdpClient provides a complete API for authentication, session management, and organization operations. It uses WebSocket connections via TypedSocket for real-time, type-safe communication with the IdP server.
|
||||
## Issue Reporting and Security
|
||||
|
||||
## Installation
|
||||
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @idp.global/idpclient
|
||||
# or
|
||||
pnpm add @idp.global/idpclient
|
||||
pnpm add @idp.global/client
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { IdpClient } from '@idp.global/idpclient';
|
||||
```ts
|
||||
import { IdpClient } from '@idp.global/client';
|
||||
|
||||
// Initialize the client
|
||||
const idpClient = new IdpClient('https://idp.global');
|
||||
|
||||
// Enable WebSocket connection
|
||||
await idpClient.enableTypedSocket();
|
||||
|
||||
// Check login status
|
||||
const isLoggedIn = await idpClient.determineLoginStatus();
|
||||
const loggedIn = await idpClient.determineLoginStatus();
|
||||
|
||||
if (isLoggedIn) {
|
||||
const userInfo = await idpClient.whoIs();
|
||||
console.log('Logged in as:', userInfo.user.data.name);
|
||||
if (!loggedIn) {
|
||||
const loginResult = await idpClient.requests.loginWithUserNameAndPassword.fire({
|
||||
username: 'user@example.com',
|
||||
password: 'secret',
|
||||
});
|
||||
|
||||
if (loginResult.refreshToken) {
|
||||
await idpClient.refreshJwt(loginResult.refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
const whoIs = await idpClient.whoIs();
|
||||
console.log(whoIs.user.data.email);
|
||||
```
|
||||
|
||||
## Core Features
|
||||
## What The Client Handles
|
||||
|
||||
### Authentication
|
||||
- Normalizes the base URL to the server's `/typedrequest` endpoint.
|
||||
- Stores JWT and refresh token state in a browser `WebStore`.
|
||||
- Refreshes expiring JWTs via `performJwtHousekeeping()`.
|
||||
- Redirects to `/login` when `determineLoginStatus(true)` is used.
|
||||
- Exchanges refresh tokens for cross-app transfer tokens.
|
||||
- Exposes the low-level typed requests through `idpClient.requests`.
|
||||
|
||||
#### Password Login
|
||||
## Common Flows
|
||||
|
||||
```typescript
|
||||
const response = await idpClient.requests.loginWithUserNameAndPassword.fire({
|
||||
### Password Login
|
||||
|
||||
```ts
|
||||
const result = await idpClient.requests.loginWithUserNameAndPassword.fire({
|
||||
username: 'user@example.com',
|
||||
password: 'securepassword',
|
||||
});
|
||||
|
||||
if (response.refreshToken) {
|
||||
await idpClient.refreshJwt(response.refreshToken);
|
||||
console.log('Login successful!');
|
||||
} else if (response.twoFaNeeded) {
|
||||
console.log('2FA verification required');
|
||||
}
|
||||
```
|
||||
|
||||
#### Magic Link Login
|
||||
|
||||
```typescript
|
||||
// Request magic link
|
||||
await idpClient.requests.loginWithEmail.fire({
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
// After clicking the email link
|
||||
const result = await idpClient.requests.loginWithEmailAfterToken.fire({
|
||||
email: 'user@example.com',
|
||||
token: 'token-from-email-link',
|
||||
password: 'secret',
|
||||
});
|
||||
|
||||
if (result.refreshToken) {
|
||||
@@ -73,303 +63,80 @@ if (result.refreshToken) {
|
||||
}
|
||||
```
|
||||
|
||||
#### API Token Login
|
||||
### Magic Link Login
|
||||
|
||||
```typescript
|
||||
const result = await idpClient.requests.loginWithApiToken.fire({
|
||||
apiToken: 'your-api-token',
|
||||
```ts
|
||||
await idpClient.requests.loginWithEmail.fire({
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
if (result.jwt) {
|
||||
await idpClient.setJwt(result.jwt);
|
||||
}
|
||||
const result = await idpClient.requests.loginWithEmailAfterToken.fire({
|
||||
email: 'user@example.com',
|
||||
token: 'token-from-email',
|
||||
});
|
||||
|
||||
await idpClient.refreshJwt(result.refreshToken);
|
||||
```
|
||||
|
||||
### Session Management
|
||||
### Session and Identity
|
||||
|
||||
```typescript
|
||||
// Get current JWT
|
||||
const jwt = await idpClient.getJwt();
|
||||
|
||||
// Get parsed JWT data
|
||||
const jwtData = await idpClient.getJwtData();
|
||||
console.log('User ID:', jwtData.id);
|
||||
|
||||
// Refresh JWT (automatic housekeeping)
|
||||
```ts
|
||||
await idpClient.performJwtHousekeeping();
|
||||
|
||||
// Manual refresh
|
||||
await idpClient.refreshJwt();
|
||||
const jwt = await idpClient.getJwt();
|
||||
const jwtData = await idpClient.getJwtData();
|
||||
const whoIs = await idpClient.whoIs();
|
||||
|
||||
// Logout
|
||||
await idpClient.logout();
|
||||
console.log(jwtData.id, whoIs.user.data.username);
|
||||
```
|
||||
|
||||
### User Information
|
||||
### Organizations
|
||||
|
||||
```typescript
|
||||
// Get current user details
|
||||
const whoIsResponse = await idpClient.whoIs();
|
||||
console.log('Name:', whoIsResponse.user.data.name);
|
||||
console.log('Email:', whoIsResponse.user.data.email);
|
||||
```ts
|
||||
const rolesAndOrganizations = await idpClient.getRolesAndOrganizations();
|
||||
|
||||
// Get user data
|
||||
const userData = await idpClient.requests.getUserData.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
userId: jwtData.id,
|
||||
});
|
||||
|
||||
// Update user data
|
||||
await idpClient.requests.setUserData.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
userId: jwtData.id,
|
||||
name: 'New Name',
|
||||
});
|
||||
```
|
||||
|
||||
### Organization Management
|
||||
|
||||
```typescript
|
||||
// Get user's organizations and roles
|
||||
const orgsAndRoles = await idpClient.getRolesAndOrganizations();
|
||||
console.log('Organizations:', orgsAndRoles.organizations);
|
||||
console.log('Roles:', orgsAndRoles.roles);
|
||||
|
||||
// Create a new organization
|
||||
const result = await idpClient.createOrganization(
|
||||
'My Company', // name
|
||||
'my-company', // slug
|
||||
'manifest' // mode: 'checkAvailability' or 'manifest'
|
||||
const created = await idpClient.createOrganization(
|
||||
'Acme',
|
||||
'acme',
|
||||
'manifest'
|
||||
);
|
||||
|
||||
if (result.resultingOrganization) {
|
||||
console.log('Created:', result.resultingOrganization.id);
|
||||
}
|
||||
|
||||
// Get organization details
|
||||
const orgDetails = await idpClient.requests.getOrganizationById.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
});
|
||||
```
|
||||
|
||||
### Member & Invitation Management
|
||||
|
||||
```typescript
|
||||
// Get organization members
|
||||
const members = await idpClient.requests.getOrgMembers.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
});
|
||||
|
||||
// Invite a new member
|
||||
await idpClient.requests.createInvitation.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
email: 'newmember@example.com',
|
||||
roles: ['member'],
|
||||
});
|
||||
|
||||
// Bulk invite members
|
||||
await idpClient.requests.bulkCreateInvitations.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
invitations: [
|
||||
{ email: 'user1@example.com', roles: ['member'] },
|
||||
{ email: 'user2@example.com', roles: ['admin'] },
|
||||
],
|
||||
});
|
||||
|
||||
// Accept an invitation
|
||||
await idpClient.requests.acceptInvitation.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
invitationToken: 'token-from-invite-email',
|
||||
});
|
||||
|
||||
// Remove a member
|
||||
await idpClient.requests.removeMember.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
// Transfer ownership
|
||||
await idpClient.requests.transferOwnership.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
newOwnerId: 'new-owner-user-id',
|
||||
organizationId: created.resultingOrganization.id,
|
||||
});
|
||||
```
|
||||
|
||||
### Password Management
|
||||
### Cross-App Transfer
|
||||
|
||||
```typescript
|
||||
// Request password reset
|
||||
await idpClient.requests.resetPassword.fire({
|
||||
email: 'user@example.com',
|
||||
});
|
||||
|
||||
// Set new password (with token from email)
|
||||
await idpClient.requests.setNewPassword.fire({
|
||||
email: 'user@example.com',
|
||||
tokenArg: 'reset-token',
|
||||
newPassword: 'newsecurepassword',
|
||||
});
|
||||
|
||||
// Change password (when logged in)
|
||||
await idpClient.requests.setNewPassword.fire({
|
||||
email: 'user@example.com',
|
||||
oldPassword: 'currentpassword',
|
||||
newPassword: 'newsecurepassword',
|
||||
});
|
||||
```
|
||||
|
||||
### Session & Device Management
|
||||
|
||||
```typescript
|
||||
// Get active sessions
|
||||
const sessions = await idpClient.requests.getUserSessions.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
userId: jwtData.id,
|
||||
});
|
||||
|
||||
// Revoke a session
|
||||
await idpClient.requests.revokeSession.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
sessionId: 'session-id',
|
||||
});
|
||||
|
||||
// Get device ID
|
||||
const deviceInfo = await idpClient.requests.obtainDeviceId.fire({});
|
||||
|
||||
// Attach device to session
|
||||
await idpClient.requests.attachDeviceId.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
deviceId: deviceInfo.deviceId.id,
|
||||
});
|
||||
```
|
||||
|
||||
### Cross-Domain Authentication
|
||||
|
||||
```typescript
|
||||
// Get transfer token for SSO between apps
|
||||
```ts
|
||||
const transferToken = await idpClient.getTransferToken();
|
||||
|
||||
// Switch to another app with authentication
|
||||
await idpClient.getTransferTokenAndSwitchToLocation('https://app.example.com/');
|
||||
|
||||
// Process incoming transfer token (in target app)
|
||||
const success = await idpClient.processTransferToken();
|
||||
if (success) {
|
||||
console.log('Cross-domain login successful');
|
||||
}
|
||||
```
|
||||
|
||||
### Billing Integration
|
||||
## Typed Request Surface
|
||||
|
||||
```typescript
|
||||
// Get billing plan for an organization
|
||||
const billingPlan = await idpClient.requests.getBillingPlan.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
organizationId: 'org-id',
|
||||
});
|
||||
`IdpRequests` exposes typed request getters for:
|
||||
|
||||
// Get Paddle configuration
|
||||
const paddleConfig = await idpClient.requests.getPaddleConfig.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
});
|
||||
- authentication
|
||||
- registration
|
||||
- user/session queries
|
||||
- org and invitation management
|
||||
- billing requests
|
||||
- JWT validation key requests
|
||||
- admin requests
|
||||
|
||||
// Update payment method
|
||||
await idpClient.updatePaddleCheckoutId('org-id', 'checkout-id');
|
||||
```
|
||||
Use these when you want full control instead of the higher-level helper methods on `IdpClient`.
|
||||
|
||||
### Admin Operations (Global Admins Only)
|
||||
## Important Runtime Notes
|
||||
|
||||
```typescript
|
||||
// Check if user is global admin
|
||||
const isAdmin = await idpClient.requests.checkGlobalAdmin.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
});
|
||||
|
||||
// Get platform statistics
|
||||
const stats = await idpClient.requests.getGlobalAppStats.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
});
|
||||
|
||||
// Create a global app
|
||||
await idpClient.requests.createGlobalApp.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
name: 'My App',
|
||||
description: 'App description',
|
||||
});
|
||||
|
||||
// Suspend a user
|
||||
await idpClient.requests.suspendUser.fire({
|
||||
jwt: await idpClient.getJwt(),
|
||||
userId: 'user-id',
|
||||
});
|
||||
```
|
||||
|
||||
## Reactive Subscriptions
|
||||
|
||||
The client provides RxJS subjects for reactive updates:
|
||||
|
||||
```typescript
|
||||
// Subscribe to login status changes
|
||||
idpClient.statusObservable.subscribe((status) => {
|
||||
console.log('Login status changed:', status);
|
||||
});
|
||||
|
||||
// Subscribe to roles updates
|
||||
idpClient.rolesReplaySubject.subscribe((roles) => {
|
||||
console.log('Roles updated:', roles);
|
||||
});
|
||||
|
||||
// Subscribe to organizations updates
|
||||
idpClient.organizationsReplaySubject.subscribe((orgs) => {
|
||||
console.log('Organizations updated:', orgs);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### IdpClient Class
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `enableTypedSocket()` | Initialize WebSocket connection |
|
||||
| `determineLoginStatus(requireLogin?)` | Check if user is logged in |
|
||||
| `getJwt()` | Get stored JWT string |
|
||||
| `getJwtData()` | Get parsed JWT data |
|
||||
| `setJwt(jwt)` | Store JWT |
|
||||
| `deleteJwt()` | Remove stored JWT |
|
||||
| `refreshJwt(refreshToken?)` | Refresh the JWT |
|
||||
| `performJwtHousekeeping()` | Auto-refresh JWT if needed |
|
||||
| `logout()` | End session and redirect |
|
||||
| `whoIs()` | Get current user info |
|
||||
| `getRolesAndOrganizations()` | Get user's orgs and roles |
|
||||
| `createOrganization(name, slug, mode)` | Create new organization |
|
||||
| `getTransferToken(appData?)` | Get SSO transfer token |
|
||||
| `processTransferToken()` | Process incoming transfer token |
|
||||
| `stop()` | Close WebSocket connection |
|
||||
|
||||
### IdpRequests Class
|
||||
|
||||
Access via `idpClient.requests.*`:
|
||||
|
||||
**Authentication**: `loginWithUserNameAndPassword`, `loginWithEmail`, `loginWithEmailAfterToken`, `loginWithApiToken`, `resetPassword`, `setNewPassword`
|
||||
|
||||
**User**: `getUserData`, `setUserData`, `getUserSessions`, `revokeSession`, `getUserActivity`
|
||||
|
||||
**Organization**: `getOrganizationById`, `updateOrganization`, `createInvitation`, `bulkCreateInvitations`, `getOrgMembers`, `getOrgInvitations`, `acceptInvitation`, `cancelInvitation`, `resendInvitation`, `removeMember`, `updateMemberRoles`, `transferOwnership`
|
||||
|
||||
**Billing**: `getBillingPlan`, `getPaddleConfig`
|
||||
|
||||
**Admin**: `checkGlobalAdmin`, `getGlobalAppStats`, `createGlobalApp`, `updateGlobalApp`, `deleteGlobalApp`, `suspendUser`, `deleteSuspendedUser`
|
||||
- The default fallback `appData` uses `window.location`, so this package is primarily browser-oriented.
|
||||
- The client expects the backend `typedrequest` websocket surface to be reachable.
|
||||
- Auth state is persisted in browser storage under the `idpglobalStore` store name.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
@@ -381,7 +148,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user