mirror of https://github.com/boxyhq/jackson.git
664 lines
17 KiB
TypeScript
664 lines
17 KiB
TypeScript
import { type JWK } from 'jose';
|
|
|
|
interface SSOConnection {
|
|
defaultRedirectUrl: string;
|
|
redirectUrl: string[] | string;
|
|
tenant: string;
|
|
product: string;
|
|
name?: string;
|
|
description?: string;
|
|
}
|
|
|
|
export interface SAMLSSOConnection extends SSOConnection {
|
|
forceAuthn?: boolean | string;
|
|
}
|
|
|
|
export interface SAMLSSOConnectionWithRawMetadata extends SAMLSSOConnection {
|
|
rawMetadata: string;
|
|
encodedRawMetadata?: never;
|
|
}
|
|
|
|
export interface SAMLSSOConnectionWithEncodedMetadata extends SAMLSSOConnection {
|
|
rawMetadata?: never;
|
|
encodedRawMetadata: string;
|
|
}
|
|
|
|
export interface OIDCSSOConnection extends SSOConnection {
|
|
oidcDiscoveryUrl: string;
|
|
oidcClientId: string;
|
|
oidcClientSecret: string;
|
|
}
|
|
|
|
export interface SAMLSSORecord extends SAMLSSOConnection {
|
|
clientID: string; // set by Jackson
|
|
clientSecret: string; // set by Jackson
|
|
idpMetadata: {
|
|
entityID: string;
|
|
loginType?: string;
|
|
provider: string | 'Unknown';
|
|
slo: {
|
|
postUrl?: string;
|
|
redirectUrl?: string;
|
|
};
|
|
sso: {
|
|
postUrl?: string;
|
|
redirectUrl?: string;
|
|
};
|
|
thumbprint?: string;
|
|
validTo?: string;
|
|
};
|
|
certs: {
|
|
privateKey: string;
|
|
publicKey: string;
|
|
};
|
|
}
|
|
|
|
export interface OIDCSSORecord extends SSOConnection {
|
|
clientID: string; // set by Jackson
|
|
clientSecret: string; // set by Jackson
|
|
oidcProvider: {
|
|
provider?: string;
|
|
discoveryUrl?: string;
|
|
clientId?: string;
|
|
clientSecret?: string;
|
|
};
|
|
}
|
|
|
|
export type ConnectionType = 'saml' | 'oidc';
|
|
|
|
type ClientIDQuery = {
|
|
clientID: string;
|
|
};
|
|
|
|
type TenantQuery = {
|
|
tenant: string;
|
|
product: string;
|
|
strategy?: ConnectionType;
|
|
};
|
|
|
|
export type GetConnectionsQuery = ClientIDQuery | TenantQuery;
|
|
export type DelConnectionsQuery = (ClientIDQuery & { clientSecret: string }) | TenantQuery;
|
|
|
|
export type GetConfigQuery = ClientIDQuery | Omit<TenantQuery, 'strategy'>;
|
|
export type DelConfigQuery = (ClientIDQuery & { clientSecret: string }) | Omit<TenantQuery, 'strategy'>;
|
|
|
|
export interface IConnectionAPIController {
|
|
/**
|
|
* @deprecated Use `createSAMLConnection` instead.
|
|
*/
|
|
config(body: SAMLSSOConnection): Promise<SAMLSSORecord>;
|
|
createSAMLConnection(
|
|
body: SAMLSSOConnectionWithRawMetadata | SAMLSSOConnectionWithEncodedMetadata
|
|
): Promise<SAMLSSORecord>;
|
|
createOIDCConnection(body: OIDCSSOConnection): Promise<OIDCSSORecord>;
|
|
/**
|
|
* @deprecated Use `updateSAMLConnection` instead.
|
|
*/
|
|
updateConfig(body: SAMLSSOConnection & { clientID: string; clientSecret: string }): Promise<void>;
|
|
updateSAMLConnection(
|
|
body: (SAMLSSOConnectionWithRawMetadata | SAMLSSOConnectionWithEncodedMetadata) & {
|
|
clientID: string;
|
|
clientSecret: string;
|
|
}
|
|
): Promise<void>;
|
|
updateOIDCConnection(body: OIDCSSOConnection & { clientID: string; clientSecret: string }): Promise<void>;
|
|
getConnections(body: GetConnectionsQuery): Promise<Array<SAMLSSORecord | OIDCSSORecord>>;
|
|
/**
|
|
* @deprecated Use `getConnections` instead.
|
|
*/
|
|
getConfig(body: GetConfigQuery): Promise<SAMLSSORecord | Record<string, never>>;
|
|
deleteConnections(body: DelConnectionsQuery): Promise<void>;
|
|
/**
|
|
* @deprecated Use `deleteConnections` instead.
|
|
*/
|
|
deleteConfig(body: DelConfigQuery): Promise<void>;
|
|
}
|
|
|
|
export interface IOAuthController {
|
|
authorize(body: OAuthReq): Promise<{ redirect_url?: string; authorize_form?: string }>;
|
|
samlResponse(body: SAMLResponsePayload): Promise<{ redirect_url?: string; app_select_form?: string }>;
|
|
oidcAuthzResponse(body: OIDCAuthzResponsePayload): Promise<{ redirect_url?: string }>;
|
|
token(body: OAuthTokenReq): Promise<OAuthTokenRes>;
|
|
userInfo(token: string): Promise<Profile>;
|
|
}
|
|
|
|
export interface IAdminController {
|
|
getAllConnection(pageOffset?: number, pageLimit?: number);
|
|
}
|
|
|
|
export interface IHealthCheckController {
|
|
status(): Promise<{
|
|
status: number;
|
|
}>;
|
|
init(): Promise<void>;
|
|
}
|
|
|
|
export interface ILogoutController {
|
|
createRequest(body: SLORequestParams): Promise<{ logoutUrl: string | null; logoutForm: string | null }>;
|
|
handleResponse(body: SAMLResponsePayload): Promise<any>;
|
|
}
|
|
|
|
export interface IOidcDiscoveryController {
|
|
openidConfig(): {
|
|
issuer: string;
|
|
authorization_endpoint: string;
|
|
token_endpoint: string;
|
|
userinfo_endpoint: string;
|
|
jwks_uri: string;
|
|
response_types_supported: Array<string>;
|
|
subject_types_supported: Array<string>;
|
|
id_token_signing_alg_values_supported: Array<string>;
|
|
grant_types_supported: Array<string>;
|
|
code_challenge_methods_supported: Array<string>;
|
|
};
|
|
|
|
jwks(): Promise<{
|
|
keys: JWK[];
|
|
}>;
|
|
}
|
|
|
|
export interface OAuthReqBody {
|
|
state: string;
|
|
response_type: 'code';
|
|
redirect_uri: string;
|
|
code_challenge: string;
|
|
code_challenge_method: 'plain' | 'S256' | '';
|
|
scope?: string;
|
|
nonce?: string;
|
|
idp_hint?: string;
|
|
prompt?: string;
|
|
}
|
|
|
|
export interface OAuthReqBodyWithClientId extends OAuthReqBody {
|
|
client_id: string;
|
|
}
|
|
|
|
export interface OAuthReqBodyWithTenantProduct extends OAuthReqBody {
|
|
client_id: 'dummy';
|
|
tenant: string;
|
|
product: string;
|
|
}
|
|
|
|
export interface OAuthReqBodyWithAccessType extends OAuthReqBody {
|
|
client_id: 'dummy';
|
|
access_type: string;
|
|
}
|
|
|
|
export interface OAuthReqBodyWithResource extends OAuthReqBody {
|
|
client_id: 'dummy';
|
|
resource: string;
|
|
}
|
|
|
|
export type OAuthReq =
|
|
| OAuthReqBodyWithClientId
|
|
| OAuthReqBodyWithTenantProduct
|
|
| OAuthReqBodyWithAccessType
|
|
| OAuthReqBodyWithResource;
|
|
|
|
export interface SAMLResponsePayload {
|
|
SAMLResponse: string;
|
|
RelayState: string;
|
|
idp_hint?: string;
|
|
}
|
|
|
|
interface OIDCAuthzResponseSuccess {
|
|
code: string;
|
|
state: string;
|
|
error?: never;
|
|
error_description?: never;
|
|
}
|
|
|
|
interface OIDCAuthzResponseError {
|
|
code?: never;
|
|
state: string;
|
|
error: OAuthErrorHandlerParams['error'] | OIDCErrorCodes;
|
|
error_description?: string;
|
|
}
|
|
|
|
export type OIDCAuthzResponsePayload = OIDCAuthzResponseSuccess | OIDCAuthzResponseError;
|
|
|
|
interface OAuthTokenReqBody {
|
|
code: string;
|
|
grant_type: 'authorization_code';
|
|
redirect_uri: string;
|
|
}
|
|
|
|
export interface OAuthTokenReqWithCodeVerifier extends OAuthTokenReqBody {
|
|
code_verifier: string;
|
|
client_id?: never;
|
|
client_secret?: never;
|
|
}
|
|
|
|
export interface OAuthTokenReqWithCredentials extends OAuthTokenReqBody {
|
|
code_verifier?: never;
|
|
client_id: string;
|
|
client_secret: string;
|
|
}
|
|
|
|
export type OAuthTokenReq = OAuthTokenReqWithCodeVerifier | OAuthTokenReqWithCredentials;
|
|
|
|
export interface OAuthTokenRes {
|
|
access_token: string;
|
|
id_token?: string;
|
|
token_type: 'bearer';
|
|
expires_in: number;
|
|
}
|
|
|
|
export interface Profile {
|
|
id: string;
|
|
sub?: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
requested: Record<string, string>;
|
|
}
|
|
|
|
export interface Index {
|
|
name: string;
|
|
value: string;
|
|
}
|
|
|
|
export interface DatabaseDriver {
|
|
getAll(namespace: string, pageOffset?: number, pageLimit?: number): Promise<unknown[]>;
|
|
get(namespace: string, key: string): Promise<any>;
|
|
put(namespace: string, key: string, val: any, ttl: number, ...indexes: Index[]): Promise<any>;
|
|
delete(namespace: string, key: string): Promise<any>;
|
|
getByIndex(namespace: string, idx: Index): Promise<any>;
|
|
}
|
|
|
|
export interface Storable {
|
|
getAll(pageOffset?: number, pageLimit?: number): Promise<unknown[]>;
|
|
get(key: string): Promise<any>;
|
|
put(key: string, val: any, ...indexes: Index[]): Promise<any>;
|
|
delete(key: string): Promise<any>;
|
|
getByIndex(idx: Index): Promise<any>;
|
|
}
|
|
|
|
export interface DatabaseStore {
|
|
store(namespace: string): Storable;
|
|
}
|
|
|
|
export interface Encrypted {
|
|
iv?: string;
|
|
tag?: string;
|
|
value: string;
|
|
}
|
|
|
|
export type EncryptionKey = any;
|
|
|
|
export type DatabaseEngine = 'redis' | 'sql' | 'mongo' | 'mem' | 'planetscale';
|
|
|
|
export type DatabaseType = 'postgres' | 'mysql' | 'mariadb';
|
|
|
|
export interface DatabaseOption {
|
|
engine?: DatabaseEngine;
|
|
url?: string;
|
|
type?: DatabaseType;
|
|
ttl?: number;
|
|
cleanupLimit?: number;
|
|
encryptionKey?: string;
|
|
pageLimit?: number;
|
|
ssl?: any;
|
|
}
|
|
|
|
export interface JacksonOption {
|
|
externalUrl: string;
|
|
samlPath: string;
|
|
oidcPath?: string;
|
|
samlAudience?: string;
|
|
preLoadedConfig?: string;
|
|
preLoadedConnection?: string;
|
|
idpEnabled?: boolean;
|
|
db: DatabaseOption;
|
|
clientSecretVerifier?: string;
|
|
idpDiscoveryPath?: string;
|
|
scimPath?: string;
|
|
openid?: {
|
|
jwsAlg?: string;
|
|
jwtSigningKeys?: {
|
|
private: string;
|
|
public: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
export interface SLORequestParams {
|
|
nameId: string;
|
|
tenant: string;
|
|
product: string;
|
|
redirectUrl?: string;
|
|
}
|
|
|
|
interface Metadata {
|
|
sso: {
|
|
postUrl?: string;
|
|
redirectUrl: string;
|
|
};
|
|
slo: {
|
|
redirectUrl?: string;
|
|
postUrl?: string;
|
|
};
|
|
entityID: string;
|
|
thumbprint: string;
|
|
loginType: 'idp' | 'sp';
|
|
provider: string;
|
|
}
|
|
|
|
export interface SAMLConnection {
|
|
idpMetadata: Metadata;
|
|
certs: {
|
|
privateKey: string;
|
|
publicKey: string;
|
|
};
|
|
defaultRedirectUrl: string;
|
|
}
|
|
|
|
// See Error Response section in https://www.oauth.com/oauth2-servers/authorization/the-authorization-response/
|
|
export interface OAuthErrorHandlerParams {
|
|
error:
|
|
| 'invalid_request'
|
|
| 'access_denied'
|
|
| 'unauthorized_client'
|
|
| 'unsupported_response_type'
|
|
| 'invalid_scope'
|
|
| 'server_error'
|
|
| 'temporarily_unavailable'
|
|
| OIDCErrorCodes;
|
|
error_description: string;
|
|
redirect_uri: string;
|
|
state?: string;
|
|
}
|
|
|
|
export type OIDCErrorCodes =
|
|
| 'interaction_required'
|
|
| 'login_required'
|
|
| 'account_selection_required'
|
|
| 'consent_required'
|
|
| 'invalid_request_uri'
|
|
| 'invalid_request_object'
|
|
| 'request_not_supported'
|
|
| 'request_uri_not_supported'
|
|
| 'registration_not_supported';
|
|
|
|
export interface ISPSAMLConfig {
|
|
get(): {
|
|
acsUrl: string;
|
|
entityId: string;
|
|
response: string;
|
|
assertionSignature: string;
|
|
signatureAlgorithm: string;
|
|
assertionEncryption: string;
|
|
};
|
|
toMarkdown(): string;
|
|
toHTML(): string;
|
|
}
|
|
|
|
export type DirectorySyncEventType =
|
|
| 'user.created'
|
|
| 'user.updated'
|
|
| 'user.deleted'
|
|
| 'group.created'
|
|
| 'group.updated'
|
|
| 'group.deleted'
|
|
| 'group.user_added'
|
|
| 'group.user_removed';
|
|
|
|
export interface Base {
|
|
store(type: 'groups' | 'members' | 'users'): Storable;
|
|
setTenant(tenant: string): this;
|
|
setProduct(product: string): this;
|
|
setTenantAndProduct(tenant: string, product: string): this;
|
|
with(tenant: string, product: string): this;
|
|
createId(): string;
|
|
}
|
|
|
|
export interface Users extends Base {
|
|
list({
|
|
pageOffset,
|
|
pageLimit,
|
|
}: {
|
|
pageOffset?: number;
|
|
pageLimit?: number;
|
|
}): Promise<{ data: User[] | null; error: ApiError | null }>;
|
|
get(id: string): Promise<{ data: User | null; error: ApiError | null }>;
|
|
search(userName: string): Promise<{ data: User[] | null; error: ApiError | null }>;
|
|
delete(id: string): Promise<{ data: null; error: ApiError | null }>;
|
|
clear(): Promise<void>;
|
|
create(param: {
|
|
first_name: string;
|
|
last_name: string;
|
|
email: string;
|
|
active: boolean;
|
|
raw: any;
|
|
}): Promise<{ data: User | null; error: ApiError | null }>;
|
|
update(
|
|
id: string,
|
|
param: {
|
|
first_name: string;
|
|
last_name: string;
|
|
email: string;
|
|
active: boolean;
|
|
raw: object;
|
|
}
|
|
): Promise<{ data: User | null; error: ApiError | null }>;
|
|
}
|
|
|
|
export interface Groups extends Base {
|
|
create(param: { name: string; raw: any }): Promise<{ data: Group | null; error: ApiError | null }>;
|
|
removeAllUsers(groupId: string): Promise<void>;
|
|
list({
|
|
pageOffset,
|
|
pageLimit,
|
|
}: {
|
|
pageOffset?: number;
|
|
pageLimit?: number;
|
|
}): Promise<{ data: Group[] | null; error: ApiError | null }>;
|
|
get(id: string): Promise<{ data: Group | null; error: ApiError | null }>;
|
|
getAllUsers(groupId: string): Promise<{ user_id: string }[]>;
|
|
delete(id: string): Promise<{ data: null; error: ApiError | null }>;
|
|
addUserToGroup(groupId: string, userId: string): Promise<void>;
|
|
isUserInGroup(groupId: string, userId: string): Promise<boolean>;
|
|
removeUserFromGroup(groupId: string, userId: string): Promise<void>;
|
|
search(displayName: string): Promise<{ data: Group[] | null; error: ApiError | null }>;
|
|
update(
|
|
id: string,
|
|
param: {
|
|
name: string;
|
|
raw: any;
|
|
}
|
|
): Promise<{ data: Group | null; error: ApiError | null }>;
|
|
}
|
|
|
|
export type User = {
|
|
id: string;
|
|
email: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
active: boolean;
|
|
raw?: any;
|
|
};
|
|
|
|
export type Group = {
|
|
id: string;
|
|
name: string;
|
|
raw?: any;
|
|
};
|
|
|
|
export enum DirectorySyncProviders {
|
|
'azure-scim-v2' = 'Azure SCIM v2.0',
|
|
'onelogin-scim-v2' = 'OneLogin SCIM v2.0',
|
|
'okta-scim-v2' = 'Okta SCIM v2.0',
|
|
'jumpcloud-scim-v2' = 'JumpCloud v2.0',
|
|
'generic-scim-v2' = 'SCIM Generic v2.0',
|
|
}
|
|
|
|
export type DirectoryType = keyof typeof DirectorySyncProviders;
|
|
|
|
export type HTTPMethod = 'POST' | 'PUT' | 'DELETE' | 'GET' | 'PATCH';
|
|
|
|
export type Directory = {
|
|
id: string;
|
|
name: string;
|
|
tenant: string;
|
|
product: string;
|
|
type: DirectoryType;
|
|
log_webhook_events: boolean;
|
|
scim: {
|
|
path: string;
|
|
endpoint?: string;
|
|
secret: string;
|
|
};
|
|
webhook: {
|
|
endpoint: string;
|
|
secret: string;
|
|
};
|
|
};
|
|
|
|
export type DirectorySyncGroupMember = { value: string; email?: string };
|
|
|
|
export interface DirectoryConfig {
|
|
create({
|
|
name,
|
|
tenant,
|
|
product,
|
|
webhook_url,
|
|
webhook_secret,
|
|
type,
|
|
}: {
|
|
name?: string;
|
|
tenant: string;
|
|
product: string;
|
|
webhook_url?: string;
|
|
webhook_secret?: string;
|
|
type?: DirectoryType;
|
|
}): Promise<{ data: Directory | null; error: ApiError | null }>;
|
|
update(
|
|
id: string,
|
|
param: Omit<Partial<Directory>, 'id' | 'tenant' | 'prodct' | 'scim'>
|
|
): Promise<{ data: Directory | null; error: ApiError | null }>;
|
|
get(id: string): Promise<{ data: Directory | null; error: ApiError | null }>;
|
|
getByTenantAndProduct(
|
|
tenant: string,
|
|
product: string
|
|
): Promise<{ data: Directory | null; error: ApiError | null }>;
|
|
list({
|
|
pageOffset,
|
|
pageLimit,
|
|
}: {
|
|
pageOffset?: number;
|
|
pageLimit?: number;
|
|
}): Promise<{ data: Directory[] | null; error: ApiError | null }>;
|
|
delete(id: string): Promise<void>;
|
|
}
|
|
|
|
export interface IDirectoryUsers {
|
|
create(directory: Directory, body: any): Promise<DirectorySyncResponse>;
|
|
get(user: User): Promise<DirectorySyncResponse>;
|
|
update(directory: Directory, user: User, body: any): Promise<DirectorySyncResponse>;
|
|
patch(directory: Directory, user: User, body: any): Promise<DirectorySyncResponse>;
|
|
delete(directory: Directory, user: User, active: boolean): Promise<DirectorySyncResponse>;
|
|
getAll(queryParams: { count: number; startIndex: number; filter?: string }): Promise<DirectorySyncResponse>;
|
|
handleRequest(request: DirectorySyncRequest, eventCallback?: EventCallback): Promise<DirectorySyncResponse>;
|
|
}
|
|
|
|
export interface IDirectoryGroups {
|
|
create(directory: Directory, body: any): Promise<DirectorySyncResponse>;
|
|
get(group: Group): Promise<DirectorySyncResponse>;
|
|
updateDisplayName(directory: Directory, group: Group, body: any): Promise<Group>;
|
|
delete(directory: Directory, group: Group): Promise<DirectorySyncResponse>;
|
|
getAll(queryParams: { filter?: string }): Promise<DirectorySyncResponse>;
|
|
addGroupMembers(
|
|
directory: Directory,
|
|
group: Group,
|
|
members: DirectorySyncGroupMember[] | undefined,
|
|
sendWebhookEvent: boolean
|
|
): Promise<void>;
|
|
removeGroupMembers(
|
|
directory: Directory,
|
|
group: Group,
|
|
members: DirectorySyncGroupMember[],
|
|
sendWebhookEvent: boolean
|
|
): Promise<void>;
|
|
addOrRemoveGroupMembers(
|
|
directory: Directory,
|
|
group: Group,
|
|
members: DirectorySyncGroupMember[]
|
|
): Promise<void>;
|
|
update(directory: Directory, group: Group, body: any): Promise<DirectorySyncResponse>;
|
|
patch(directory: Directory, group: Group, body: any): Promise<DirectorySyncResponse>;
|
|
handleRequest(request: DirectorySyncRequest, eventCallback?: EventCallback): Promise<DirectorySyncResponse>;
|
|
}
|
|
|
|
export interface IWebhookEventsLogger extends Base {
|
|
log(directory: Directory, event: DirectorySyncEvent): Promise<WebhookEventLog>;
|
|
getAll(): Promise<WebhookEventLog[]>;
|
|
get(id: string): Promise<WebhookEventLog>;
|
|
clear(): Promise<void>;
|
|
delete(id: string): Promise<void>;
|
|
updateStatus(log: WebhookEventLog, statusCode: number): Promise<WebhookEventLog>;
|
|
}
|
|
|
|
export type DirectorySyncResponse = {
|
|
status: number;
|
|
data?: any;
|
|
};
|
|
|
|
export interface DirectorySyncRequestHandler {
|
|
handle(request: DirectorySyncRequest, callback?: EventCallback): Promise<DirectorySyncResponse>;
|
|
}
|
|
|
|
export interface Events {
|
|
handle(event: DirectorySyncEvent): Promise<void>;
|
|
}
|
|
|
|
export interface DirectorySyncRequest {
|
|
method: HTTPMethod;
|
|
body: any | undefined;
|
|
directoryId: Directory['id'];
|
|
resourceType: 'users' | 'groups';
|
|
resourceId: string | undefined;
|
|
apiSecret: string | null;
|
|
query: {
|
|
count?: number;
|
|
startIndex?: number;
|
|
filter?: string;
|
|
};
|
|
}
|
|
|
|
export type DirectorySync = {
|
|
requests: DirectorySyncRequestHandler;
|
|
directories: DirectoryConfig;
|
|
groups: Groups;
|
|
users: Users;
|
|
events: { callback: EventCallback };
|
|
webhookLogs: IWebhookEventsLogger;
|
|
providers: () => {
|
|
[K in string]: string;
|
|
};
|
|
};
|
|
|
|
export interface ApiError {
|
|
message: string;
|
|
code: number;
|
|
}
|
|
|
|
export interface DirectorySyncEvent {
|
|
directory_id: Directory['id'];
|
|
event: DirectorySyncEventType;
|
|
data: User | Group | (User & { group: Group });
|
|
tenant: string;
|
|
product: string;
|
|
}
|
|
|
|
export interface EventCallback {
|
|
(event: DirectorySyncEvent): Promise<void>;
|
|
}
|
|
|
|
export interface WebhookEventLog extends DirectorySyncEvent {
|
|
id: string;
|
|
webhook_endpoint: string;
|
|
created_at: Date;
|
|
status_code?: number;
|
|
delivered?: boolean;
|
|
}
|