@kmlckj/licos-platform-sdk 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,7 +94,7 @@ const file = await storage.uploadFile('/tmp/report.pdf');
94
94
  const link = await storage.shareUrl(file.id);
95
95
  ```
96
96
 
97
- `uploadFile` limits each file to 20 MiB.
97
+ `uploadFile` limits each file to 100 MiB. Payloads larger than 8 MiB automatically use the platform chunk-upload protocol.
98
98
 
99
99
  The browser entry does not export object storage helpers because runtime tokens
100
100
  must not be bundled into public frontend code.
@@ -119,58 +119,58 @@ const results = await knowledge.search('How do I reset my password?', {
119
119
  });
120
120
  ```
121
121
 
122
- The browser entry does not export knowledge helpers because runtime tokens must
123
- not be bundled into public frontend code.
124
-
125
- ## OAuth2 Application Management
126
-
127
- OAuth2 management is available only from trusted Node runtime code. It uses the
128
- current project owner's platform identity and is not exported by the browser
129
- entry.
130
-
131
- ```js
132
- import { oauth } from '@kmlckj/licos-platform-sdk';
133
-
134
- const apps = await oauth.listApps();
135
- const app = await oauth.ensureProjectApp({
136
- appId: apps.list?.[0]?.id,
137
- name: 'Project login',
138
- loginAudience: 'ALL',
139
- redirectUriConfigs: [
140
- { environment: 'prod', redirectUri: 'https://app.example/auth/callback' },
141
- ],
142
- });
143
- ```
144
-
145
- `ensureProjectApp` requires an explicit login audience and at least one
146
- callback. Store only public PKCE settings in project configuration; never put
147
- the returned client secret or a platform user token in browser code.
148
-
149
- ## OAuth2 Project Runtime
150
-
151
- Project server routes use `oauthRuntime` for Authorization Code + PKCE. The
152
- runtime module reads `config/licos-oauth.json`; it is intentionally absent from
153
- the browser entry.
154
-
155
- ```js
156
- import { oauthRuntime } from '@kmlckj/licos-platform-sdk';
157
-
158
- const config = await oauthRuntime.loadConfig();
159
- const attempt = oauthRuntime.createAuthorizationRequest(config, {
160
- redirectUri: 'https://app.example/api/auth/licos/callback',
161
- });
162
-
163
- // Persist a hash of attempt.state plus codeVerifier, redirectUri and expiresAt
164
- // in a one-time server-side record before redirecting the browser.
165
-
166
- const completed = await oauthRuntime.completeAuthorization(config, {
167
- code,
168
- redirectUri: stored.redirectUri,
169
- codeVerifier: stored.codeVerifier,
170
- });
171
- ```
172
-
173
- `completeAuthorization` returns platform tokens and userinfo to trusted server
174
- code. Map `completed.user.sub` to the project's account, create the project's
175
- own session, then revoke tokens unless the application explicitly needs
176
- delegated platform API access. Never return these tokens to the browser.
122
+ The browser entry does not export knowledge helpers because runtime tokens must
123
+ not be bundled into public frontend code.
124
+
125
+ ## OAuth2 Application Management
126
+
127
+ OAuth2 management is available only from trusted Node runtime code. It uses the
128
+ current project owner's platform identity and is not exported by the browser
129
+ entry.
130
+
131
+ ```js
132
+ import { oauth } from '@kmlckj/licos-platform-sdk';
133
+
134
+ const apps = await oauth.listApps();
135
+ const app = await oauth.ensureProjectApp({
136
+ appId: apps.list?.[0]?.id,
137
+ name: 'Project login',
138
+ loginAudience: 'ALL',
139
+ redirectUriConfigs: [
140
+ { environment: 'prod', redirectUri: 'https://app.example/auth/callback' },
141
+ ],
142
+ });
143
+ ```
144
+
145
+ `ensureProjectApp` requires an explicit login audience and at least one
146
+ callback. Store only public PKCE settings in project configuration; never put
147
+ the returned client secret or a platform user token in browser code.
148
+
149
+ ## OAuth2 Project Runtime
150
+
151
+ Project server routes use `oauthRuntime` for Authorization Code + PKCE. The
152
+ runtime module reads `config/licos-oauth.json`; it is intentionally absent from
153
+ the browser entry.
154
+
155
+ ```js
156
+ import { oauthRuntime } from '@kmlckj/licos-platform-sdk';
157
+
158
+ const config = await oauthRuntime.loadConfig();
159
+ const attempt = oauthRuntime.createAuthorizationRequest(config, {
160
+ redirectUri: 'https://app.example/api/auth/licos/callback',
161
+ });
162
+
163
+ // Persist a hash of attempt.state plus codeVerifier, redirectUri and expiresAt
164
+ // in a one-time server-side record before redirecting the browser.
165
+
166
+ const completed = await oauthRuntime.completeAuthorization(config, {
167
+ code,
168
+ redirectUri: stored.redirectUri,
169
+ codeVerifier: stored.codeVerifier,
170
+ });
171
+ ```
172
+
173
+ `completeAuthorization` returns platform tokens and userinfo to trusted server
174
+ code. Map `completed.user.sub` to the project's account, create the project's
175
+ own session, then revoke tokens unless the application explicitly needs
176
+ delegated platform API access. Never return these tokens to the browser.
package/dist/index.d.ts CHANGED
@@ -26,166 +26,170 @@ export interface RuntimeConfig {
26
26
  }
27
27
 
28
28
  export function runtimeConfig(options?: { environmentOverrideEnv?: string }): Promise<RuntimeConfig>;
29
- export function authHeaders(config: RuntimeConfig, contentType?: string): Record<string, string>;
30
-
31
- export type OAuthLoginAudience = 'ALL' | 'OWNER_TENANT';
32
-
33
- export interface OAuthRedirectUriConfig {
34
- environment?: 'dev' | 'prod';
35
- redirectUri: string;
36
- }
37
-
38
- export interface OAuthApp {
39
- id: string;
40
- tenantId?: string;
41
- appCode?: string;
42
- name?: string;
43
- description?: string;
44
- clientId?: string;
45
- loginAudience?: OAuthLoginAudience;
46
- redirectUris?: string[];
47
- redirectUriConfigs?: OAuthRedirectUriConfig[];
48
- allowedScopes?: string[];
49
- secret?: string;
50
- status?: string;
51
- [key: string]: unknown;
52
- }
53
-
54
- export interface OAuthAppList {
55
- list?: OAuthApp[];
56
- total?: number;
57
- page?: number;
58
- pageSize?: number;
59
- hasNext?: boolean;
60
- }
61
-
62
- export interface CreateOAuthAppOptions {
63
- description?: string;
64
- appCode?: string;
65
- app_code?: string;
66
- loginAudience?: OAuthLoginAudience;
67
- login_audience?: OAuthLoginAudience;
68
- redirectUriConfigs?: OAuthRedirectUriConfig[];
69
- redirect_uri_configs?: OAuthRedirectUriConfig[];
70
- redirectUris?: string[];
71
- redirect_uris?: string[];
72
- allowedScopes?: string[];
73
- allowed_scopes?: string[];
74
- appType?: string;
75
- app_type?: string;
76
- consentRequired?: boolean;
77
- consent_required?: boolean;
78
- }
79
-
80
- export interface EnsureProjectOAuthAppOptions extends CreateOAuthAppOptions {
81
- name: string;
82
- loginAudience: OAuthLoginAudience;
83
- appId?: string;
84
- app_id?: string;
85
- redirectUriConfigs: OAuthRedirectUriConfig[];
86
- }
87
-
88
- export function listOAuthApps(options?: { page?: number; pageSize?: number }): Promise<OAuthAppList>;
89
- export function getOAuthApp(appId: string): Promise<OAuthApp>;
90
- export function createOAuthApp(name: string, options?: CreateOAuthAppOptions): Promise<OAuthApp>;
91
- export function updateOAuthApp(appId: string, changes?: Record<string, unknown>): Promise<OAuthApp>;
92
- export function deleteOAuthApp(appId: string): Promise<void>;
93
- export function setOAuthAppEnabled(appId: string, enabled: boolean): Promise<OAuthApp>;
94
- export function rotateOAuthAppSecret(appId: string): Promise<OAuthApp>;
95
- export function listOAuthGrants(appId: string): Promise<Array<Record<string, unknown>>>;
96
- export function revokeOAuthGrant(grantId: string): Promise<void>;
97
- export function findOAuthAppByCode(appCode: string, options?: { pageSize?: number }): Promise<OAuthApp | null>;
98
- export function ensureProjectOAuthApp(options: EnsureProjectOAuthAppOptions): Promise<OAuthApp>;
99
-
100
- export const oauth: {
101
- listApps: typeof listOAuthApps;
102
- getApp: typeof getOAuthApp;
103
- createApp: typeof createOAuthApp;
104
- updateApp: typeof updateOAuthApp;
105
- deleteApp: typeof deleteOAuthApp;
106
- setEnabled: typeof setOAuthAppEnabled;
107
- rotateSecret: typeof rotateOAuthAppSecret;
108
- listGrants: typeof listOAuthGrants;
109
- revokeGrant: typeof revokeOAuthGrant;
110
- findAppByCode: typeof findOAuthAppByCode;
111
- ensureProjectApp: typeof ensureProjectOAuthApp;
112
- };
113
-
114
- export class OAuthProtocolError extends ApiError {
115
- error?: string;
116
- }
117
-
118
- export interface OAuthRuntimeConfig {
119
- clientId: string;
120
- publicBaseUrl: string;
121
- scopes: readonly string[];
122
- redirectUris: readonly string[];
123
- endpoints: Readonly<Record<string, string>>;
124
- appId?: string;
125
- appCode?: string;
126
- loginAudience?: OAuthLoginAudience;
127
- }
128
-
129
- export interface OAuthAuthorizationRequest {
130
- authorizationUrl: string;
131
- state: string;
132
- codeVerifier: string;
133
- redirectUri: string;
134
- expiresAt: number;
135
- }
136
-
137
- export interface OAuthTokenResponse {
138
- access_token: string;
139
- token_type?: string;
140
- expires_in?: number;
141
- refresh_token?: string;
142
- scope?: string;
143
- id_token?: string;
144
- [key: string]: unknown;
145
- }
146
-
147
- export function loadOAuthRuntimeConfig(options?: { configPath?: string }): Promise<OAuthRuntimeConfig>;
148
- export function createOAuthAuthorizationRequest(
149
- config: OAuthRuntimeConfig,
150
- options: {
151
- redirectUri: string;
152
- scopes?: string[];
153
- state?: string;
154
- codeVerifier?: string;
155
- ttlSeconds?: number;
156
- now?: number;
157
- },
158
- ): OAuthAuthorizationRequest;
159
- export function exchangeOAuthAuthorizationCode(
160
- config: OAuthRuntimeConfig,
161
- options: { code: string; redirectUri: string; codeVerifier: string },
162
- ): Promise<OAuthTokenResponse>;
163
- export function refreshOAuthAccessToken(
164
- config: OAuthRuntimeConfig,
165
- options: { refreshToken: string; scopes?: string[] },
166
- ): Promise<OAuthTokenResponse>;
167
- export function getOAuthUserInfo(
168
- config: OAuthRuntimeConfig,
169
- options: { accessToken: string },
170
- ): Promise<Record<string, unknown>>;
171
- export function revokeOAuthToken(
172
- config: OAuthRuntimeConfig,
173
- options: { token: string; tokenTypeHint?: 'access_token' | 'refresh_token' | string },
174
- ): Promise<void>;
175
- export function completeOAuthAuthorization(
176
- config: OAuthRuntimeConfig,
177
- options: { code: string; redirectUri: string; codeVerifier: string },
178
- ): Promise<{ tokens: OAuthTokenResponse; user: Record<string, unknown> }>;
179
-
180
- export const oauthRuntime: {
181
- loadConfig: typeof loadOAuthRuntimeConfig;
182
- createAuthorizationRequest: typeof createOAuthAuthorizationRequest;
183
- exchangeAuthorizationCode: typeof exchangeOAuthAuthorizationCode;
184
- refreshAccessToken: typeof refreshOAuthAccessToken;
185
- getUserInfo: typeof getOAuthUserInfo;
186
- revokeToken: typeof revokeOAuthToken;
187
- completeAuthorization: typeof completeOAuthAuthorization;
188
- };
29
+ export function authHeaders(config: RuntimeConfig, contentType?: string): Record<string, string>;
30
+
31
+ export type OAuthLoginAudience = 'ALL' | 'OWNER_TENANT';
32
+
33
+ export interface OAuthRedirectUriConfig {
34
+ environment?: 'dev' | 'prod';
35
+ redirectUri: string;
36
+ }
37
+
38
+ export interface OAuthApp {
39
+ id: string;
40
+ tenantId?: string;
41
+ appCode?: string;
42
+ name?: string;
43
+ description?: string;
44
+ clientId?: string;
45
+ loginAudience?: OAuthLoginAudience;
46
+ redirectUris?: string[];
47
+ redirectUriConfigs?: OAuthRedirectUriConfig[];
48
+ allowedScopes?: string[];
49
+ secret?: string;
50
+ status?: string;
51
+ [key: string]: unknown;
52
+ }
53
+
54
+ export interface OAuthAppList {
55
+ list?: OAuthApp[];
56
+ total?: number;
57
+ page?: number;
58
+ pageSize?: number;
59
+ hasNext?: boolean;
60
+ }
61
+
62
+ export interface CreateOAuthAppOptions {
63
+ description?: string;
64
+ appCode?: string;
65
+ app_code?: string;
66
+ loginAudience?: OAuthLoginAudience;
67
+ login_audience?: OAuthLoginAudience;
68
+ redirectUriConfigs?: OAuthRedirectUriConfig[];
69
+ redirect_uri_configs?: OAuthRedirectUriConfig[];
70
+ redirectUris?: string[];
71
+ redirect_uris?: string[];
72
+ allowedScopes?: string[];
73
+ allowed_scopes?: string[];
74
+ appType?: string;
75
+ app_type?: string;
76
+ consentRequired?: boolean;
77
+ consent_required?: boolean;
78
+ }
79
+
80
+ export interface EnsureProjectOAuthAppOptions extends CreateOAuthAppOptions {
81
+ name: string;
82
+ loginAudience: OAuthLoginAudience;
83
+ appId?: string;
84
+ app_id?: string;
85
+ redirectUriConfigs: OAuthRedirectUriConfig[];
86
+ }
87
+
88
+ export function listOAuthApps(options?: { page?: number; pageSize?: number }): Promise<OAuthAppList>;
89
+ export function getOAuthApp(appId: string): Promise<OAuthApp>;
90
+ export function createOAuthApp(name: string, options?: CreateOAuthAppOptions): Promise<OAuthApp>;
91
+ export function updateOAuthApp(appId: string, changes?: Record<string, unknown>): Promise<OAuthApp>;
92
+ export function deleteOAuthApp(appId: string): Promise<void>;
93
+ export function setOAuthAppEnabled(appId: string, enabled: boolean): Promise<OAuthApp>;
94
+ export function rotateOAuthAppSecret(appId: string): Promise<OAuthApp>;
95
+ export function listOAuthGrants(appId: string): Promise<Array<Record<string, unknown>>>;
96
+ export function revokeOAuthGrant(grantId: string): Promise<void>;
97
+ export function findOAuthAppByCode(appCode: string, options?: { pageSize?: number }): Promise<OAuthApp | null>;
98
+ export function ensureProjectOAuthApp(options: EnsureProjectOAuthAppOptions): Promise<OAuthApp>;
99
+
100
+ export const oauth: {
101
+ listApps: typeof listOAuthApps;
102
+ getApp: typeof getOAuthApp;
103
+ createApp: typeof createOAuthApp;
104
+ updateApp: typeof updateOAuthApp;
105
+ deleteApp: typeof deleteOAuthApp;
106
+ setEnabled: typeof setOAuthAppEnabled;
107
+ rotateSecret: typeof rotateOAuthAppSecret;
108
+ listGrants: typeof listOAuthGrants;
109
+ revokeGrant: typeof revokeOAuthGrant;
110
+ findAppByCode: typeof findOAuthAppByCode;
111
+ ensureProjectApp: typeof ensureProjectOAuthApp;
112
+ };
113
+
114
+ export class OAuthProtocolError extends ApiError {
115
+ error?: string;
116
+ }
117
+
118
+ export interface OAuthRuntimeConfig {
119
+ clientId: string;
120
+ publicBaseUrl: string;
121
+ scopes: readonly string[];
122
+ redirectUriConfigs: readonly OAuthRedirectUriConfig[];
123
+ redirectUris: readonly string[];
124
+ endpoints: Readonly<Record<string, string>>;
125
+ appId?: string;
126
+ appCode?: string;
127
+ loginAudience?: OAuthLoginAudience;
128
+ }
129
+
130
+ export function oauthRedirectUriForEnvironment(config: OAuthRuntimeConfig, environment: string): string;
131
+
132
+ export interface OAuthAuthorizationRequest {
133
+ authorizationUrl: string;
134
+ state: string;
135
+ codeVerifier: string;
136
+ redirectUri: string;
137
+ expiresAt: number;
138
+ }
139
+
140
+ export interface OAuthTokenResponse {
141
+ access_token: string;
142
+ token_type?: string;
143
+ expires_in?: number;
144
+ refresh_token?: string;
145
+ scope?: string;
146
+ id_token?: string;
147
+ [key: string]: unknown;
148
+ }
149
+
150
+ export function loadOAuthRuntimeConfig(options?: { configPath?: string }): Promise<OAuthRuntimeConfig>;
151
+ export function createOAuthAuthorizationRequest(
152
+ config: OAuthRuntimeConfig,
153
+ options: {
154
+ redirectUri: string;
155
+ scopes?: string[];
156
+ state?: string;
157
+ codeVerifier?: string;
158
+ ttlSeconds?: number;
159
+ now?: number;
160
+ },
161
+ ): OAuthAuthorizationRequest;
162
+ export function exchangeOAuthAuthorizationCode(
163
+ config: OAuthRuntimeConfig,
164
+ options: { code: string; redirectUri: string; codeVerifier: string },
165
+ ): Promise<OAuthTokenResponse>;
166
+ export function refreshOAuthAccessToken(
167
+ config: OAuthRuntimeConfig,
168
+ options: { refreshToken: string; scopes?: string[] },
169
+ ): Promise<OAuthTokenResponse>;
170
+ export function getOAuthUserInfo(
171
+ config: OAuthRuntimeConfig,
172
+ options: { accessToken: string },
173
+ ): Promise<Record<string, unknown>>;
174
+ export function revokeOAuthToken(
175
+ config: OAuthRuntimeConfig,
176
+ options: { token: string; tokenTypeHint?: 'access_token' | 'refresh_token' | string },
177
+ ): Promise<void>;
178
+ export function completeOAuthAuthorization(
179
+ config: OAuthRuntimeConfig,
180
+ options: { code: string; redirectUri: string; codeVerifier: string },
181
+ ): Promise<{ tokens: OAuthTokenResponse; user: Record<string, unknown> }>;
182
+
183
+ export const oauthRuntime: {
184
+ loadConfig: typeof loadOAuthRuntimeConfig;
185
+ redirectUriForEnvironment: typeof oauthRedirectUriForEnvironment;
186
+ createAuthorizationRequest: typeof createOAuthAuthorizationRequest;
187
+ exchangeAuthorizationCode: typeof exchangeOAuthAuthorizationCode;
188
+ refreshAccessToken: typeof refreshOAuthAccessToken;
189
+ getUserInfo: typeof getOAuthUserInfo;
190
+ revokeToken: typeof revokeOAuthToken;
191
+ completeAuthorization: typeof completeOAuthAuthorization;
192
+ };
189
193
 
190
194
  export const DataSourceType: {
191
195
  TEXT: 0;
@@ -1 +1 @@
1
- import{createHash as e,randomBytes as t}from"node:crypto";import{readFile as r}from"node:fs/promises";import o from"node:path";import{ApiError as n}from"./shared.js";const i=["authorization","token","userinfo","revoke"],s=/^[A-Za-z0-9._~-]{43,128}$/;export class OAuthProtocolError extends n{constructor(e,t={}){super(e,t),this.error=t.error}}function c(e,t){const r=String(e||"").trim();let o;try{o=new URL(r)}catch{throw new TypeError(`${t} must be an absolute http(s) URL`)}if(!["http:","https:"].includes(o.protocol))throw new TypeError(`${t} must be an absolute http(s) URL`);if(o.hash)throw new TypeError(`${t} must not contain a fragment`);return r}function a(e){return new URL(e).origin.toLowerCase()}function u(e,t,r){const o=String(t||"").trim();if(!o)throw new TypeError(`OAuth endpoint is missing: ${r}`);const n=c(new URL(o,`${e}/`).toString(),`endpoints.${r}`);if(a(n)!==a(e))throw new TypeError(`OAuth endpoint ${r} must use the same origin as publicBaseUrl`);return n}function h(e){return!(!e||"object"!=typeof e)&&(Array.isArray(e)?e.some(h):Object.entries(e).some(([e,t])=>e.toLowerCase().replaceAll("_","").includes("secret")||h(t)))}export async function loadOAuthRuntimeConfig({configPath:e}={}){const t=o.resolve(process.env.LICOS_PROJECT_PATH||process.cwd()),n=o.resolve(e||o.join(t,"config/licos-oauth.json"));let s;try{s=JSON.parse(await r(n,"utf8"))}catch(e){if("ENOENT"===e?.code)throw new TypeError(`OAuth config not found: ${n}`);if(e instanceof SyntaxError)throw new TypeError(`OAuth config is not valid JSON: ${n}`);throw e}if(!s||"object"!=typeof s||Array.isArray(s))throw new TypeError("OAuth config must be a JSON object");if(h(s))throw new TypeError("OAuth config must not contain client secret fields");if("licos-oauth2"!==s.provider)throw new TypeError("OAuth config provider must be licos-oauth2");if(!0!==s.pkce)throw new TypeError("OAuth config must enable PKCE");const a=String(s.clientId||"").trim();if(!a)throw new TypeError("OAuth config is missing clientId");const p=c(s.publicBaseUrl,"publicBaseUrl").replace(/\/$/,"");if(!Array.isArray(s.scopes)||0===s.scopes.length||s.scopes.some(e=>!String(e).trim()))throw new TypeError("OAuth config scopes must be a non-empty string array");if(!Array.isArray(s.redirectUriConfigs)||0===s.redirectUriConfigs.length)throw new TypeError("OAuth config redirectUriConfigs must not be empty");const f=[...new Set(s.scopes.map(e=>String(e).trim()))],d=[...new Set(s.redirectUriConfigs.map(e=>c(e?.redirectUri,"redirectUri")))];if(!s.endpoints||"object"!=typeof s.endpoints||Array.isArray(s.endpoints))throw new TypeError("OAuth config endpoints must be an object");const l=Object.fromEntries(Object.entries(s.endpoints).map(([e,t])=>[e,u(p,t,e)]));for(const e of i)if(!l[e])throw new TypeError(`OAuth endpoint is missing: ${e}`);return Object.freeze({clientId:a,publicBaseUrl:p,scopes:Object.freeze(f),redirectUris:Object.freeze(d),endpoints:Object.freeze(l),appId:String(s.appId||"").trim()||void 0,appCode:String(s.appCode||"").trim()||void 0,loginAudience:String(s.loginAudience||"").trim()||void 0})}function p(e,t){const r=c(t,"redirectUri");if(!e.redirectUris.includes(r))throw new TypeError("redirectUri is not configured for this OAuth application");return r}function f(e){if(!s.test(e))throw new TypeError("codeVerifier must be 43-128 RFC 7636 unreserved characters");return e}function d(e,t){const r=[...new Set((t||e.scopes).map(e=>String(e).trim()).filter(Boolean))];if(0===r.length)throw new TypeError("at least one OAuth scope is required");const o=r.filter(t=>!e.scopes.includes(t));if(o.length)throw new TypeError(`OAuth scopes are not configured: ${o.sort().join(", ")}`);return r}export function createOAuthAuthorizationRequest(r,o){const n=p(r,o?.redirectUri),i=d(r,o?.scopes),s=String(o?.state||t(32).toString("base64url")).trim();if(!s)throw new TypeError("state is required");const c=f(o?.codeVerifier||t(64).toString("base64url")),a=o?.ttlSeconds??600;if(!Number.isInteger(a)||a<=0)throw new TypeError("ttlSeconds must be a positive integer");const u=e("sha256").update(c,"ascii").digest("base64url"),h=new URL(r.endpoints.authorization);h.search=new URLSearchParams({response_type:"code",client_id:r.clientId,redirect_uri:n,scope:i.join(" "),state:s,code_challenge:u,code_challenge_method:"S256"}).toString();const l=o?.now??Math.floor(Date.now()/1e3);return{authorizationUrl:h.toString(),state:s,codeVerifier:c,redirectUri:n,expiresAt:l+a}}function l(e,t){let r;try{r=e?JSON.parse(e):void 0}catch{r=void 0}const o=r&&"object"==typeof r&&String(r.error||"").trim()||void 0,n=r&&"object"==typeof r&&(r.error_description||r.message||o)||e||"OAuth request failed";return new OAuthProtocolError(String(n),{status:t,error:o,details:r})}async function w(e,{method:t,form:r,accessToken:o,allowEmpty:n=!1}){const i={Accept:"application/json"};let s,c;r&&(s=new URLSearchParams(Object.entries(r).filter(([,e])=>null!=e)).toString(),i["Content-Type"]="application/x-www-form-urlencoded"),o&&(i.Authorization=`Bearer ${o}`);try{c=await fetch(e,{method:t,headers:i,body:s})}catch(e){throw new OAuthProtocolError(`OAuth request failed: ${e?.message||e}`)}const a=await c.text();if(!c.ok)throw l(a,c.status);if(!a){if(n)return;throw new OAuthProtocolError("OAuth response body is empty",{status:c.status})}let u;try{u=JSON.parse(a)}catch{throw new OAuthProtocolError("OAuth response is not valid JSON",{status:c.status})}if(!u||"object"!=typeof u||Array.isArray(u))throw new OAuthProtocolError("OAuth response is not an object",{status:c.status,details:u});if(u.error)throw l(a,c.status);return u}export async function exchangeOAuthAuthorizationCode(e,t){const r=String(t?.code||"").trim();if(!r)throw new TypeError("authorization code is required");const o=p(e,t?.redirectUri),n=f(t?.codeVerifier);return w(e.endpoints.token,{method:"POST",form:{grant_type:"authorization_code",client_id:e.clientId,code:r,redirect_uri:o,code_verifier:n}})}export async function refreshOAuthAccessToken(e,t){const r=String(t?.refreshToken||"").trim();if(!r)throw new TypeError("refreshToken is required");return w(e.endpoints.token,{method:"POST",form:{grant_type:"refresh_token",client_id:e.clientId,refresh_token:r,scope:t?.scopes?d(e,t.scopes).join(" "):void 0}})}export async function getOAuthUserInfo(e,t){const r=String(t?.accessToken||"").trim();if(!r)throw new TypeError("accessToken is required");return w(e.endpoints.userinfo,{method:"GET",accessToken:r})}export async function revokeOAuthToken(e,t){const r=String(t?.token||"").trim();if(!r)throw new TypeError("token is required");await w(e.endpoints.revoke,{method:"POST",form:{client_id:e.clientId,token:r,token_type_hint:t?.tokenTypeHint},allowEmpty:!0})}export async function completeOAuthAuthorization(e,t){const r=await exchangeOAuthAuthorizationCode(e,t),o=String(r.access_token||"").trim();if(!o)throw new OAuthProtocolError("OAuth token response is missing access_token",{details:r});return{tokens:r,user:await getOAuthUserInfo(e,{accessToken:o})}}export const oauthRuntime={loadConfig:loadOAuthRuntimeConfig,createAuthorizationRequest:createOAuthAuthorizationRequest,exchangeAuthorizationCode:exchangeOAuthAuthorizationCode,refreshAccessToken:refreshOAuthAccessToken,getUserInfo:getOAuthUserInfo,revokeToken:revokeOAuthToken,completeAuthorization:completeOAuthAuthorization};
1
+ import{createHash as e,randomBytes as r}from"node:crypto";import{readFile as t}from"node:fs/promises";import o from"node:path";import{ApiError as n}from"./shared.js";const i=["authorization","token","userinfo","revoke"],s=/^[A-Za-z0-9._~-]{43,128}$/;export class OAuthProtocolError extends n{constructor(e,r={}){super(e,r),this.error=r.error}}function c(e,r){const t=String(e||"").trim();let o;try{o=new URL(t)}catch{throw new TypeError(`${r} must be an absolute http(s) URL`)}if(!["http:","https:"].includes(o.protocol))throw new TypeError(`${r} must be an absolute http(s) URL`);if(o.hash)throw new TypeError(`${r} must not contain a fragment`);return t}function a(e){return new URL(e).origin.toLowerCase()}function u(e,r,t){const o=String(r||"").trim();if(!o)throw new TypeError(`OAuth endpoint is missing: ${t}`);const n=c(new URL(o,`${e}/`).toString(),`endpoints.${t}`);if(a(n)!==a(e))throw new TypeError(`OAuth endpoint ${t} must use the same origin as publicBaseUrl`);return n}function h(e){return!(!e||"object"!=typeof e)&&(Array.isArray(e)?e.some(h):Object.entries(e).some(([e,r])=>e.toLowerCase().replaceAll("_","").includes("secret")||h(r)))}export async function loadOAuthRuntimeConfig({configPath:e}={}){const r=o.resolve(process.env.LICOS_PROJECT_PATH||process.cwd()),n=o.resolve(e||o.join(r,"config/licos-oauth.json"));let s;try{s=JSON.parse(await t(n,"utf8"))}catch(e){if("ENOENT"===e?.code)throw new TypeError(`OAuth config not found: ${n}`);if(e instanceof SyntaxError)throw new TypeError(`OAuth config is not valid JSON: ${n}`);throw e}if(!s||"object"!=typeof s||Array.isArray(s))throw new TypeError("OAuth config must be a JSON object");if(h(s))throw new TypeError("OAuth config must not contain client secret fields");if("licos-oauth2"!==s.provider)throw new TypeError("OAuth config provider must be licos-oauth2");if(!0!==s.pkce)throw new TypeError("OAuth config must enable PKCE");const a=String(s.clientId||"").trim();if(!a)throw new TypeError("OAuth config is missing clientId");const p=c(s.publicBaseUrl,"publicBaseUrl").replace(/\/$/,"");if(!Array.isArray(s.scopes)||0===s.scopes.length||s.scopes.some(e=>!String(e).trim()))throw new TypeError("OAuth config scopes must be a non-empty string array");if(!Array.isArray(s.redirectUriConfigs)||0===s.redirectUriConfigs.length)throw new TypeError("OAuth config redirectUriConfigs must not be empty");const d=[...new Set(s.scopes.map(e=>String(e).trim()))],f=s.redirectUriConfigs.map(e=>{if(!e||"object"!=typeof e||Array.isArray(e))throw new TypeError("OAuth redirect config must be an object");const r=String(e.environment||"").trim().toLowerCase();if(!["dev","prod"].includes(r))throw new TypeError("OAuth redirect config environment must be dev or prod");return Object.freeze({environment:r,redirectUri:c(e.redirectUri,"redirectUri")})});if(new Set(f.map(e=>e.environment)).size!==f.length)throw new TypeError("OAuth redirect config environments must be unique");const w=[...new Set(f.map(e=>e.redirectUri))];if(!s.endpoints||"object"!=typeof s.endpoints||Array.isArray(s.endpoints))throw new TypeError("OAuth config endpoints must be an object");const l=Object.fromEntries(Object.entries(s.endpoints).map(([e,r])=>[e,u(p,r,e)]));for(const e of i)if(!l[e])throw new TypeError(`OAuth endpoint is missing: ${e}`);return Object.freeze({clientId:a,publicBaseUrl:p,scopes:Object.freeze(d),redirectUriConfigs:Object.freeze(f),redirectUris:Object.freeze(w),endpoints:Object.freeze(l),appId:String(s.appId||"").trim()||void 0,appCode:String(s.appCode||"").trim()||void 0,loginAudience:String(s.loginAudience||"").trim()||void 0})}export function oauthRedirectUriForEnvironment(e,r){const t=String(r||"").trim().toLowerCase(),o="production"===t?"prod":"development"===t?"dev":t;if(!["dev","prod"].includes(o))throw new TypeError("OAuth environment must be dev or prod");const n=e.redirectUriConfigs?.find(e=>e.environment===o);if(!n)throw new TypeError(`OAuth redirect URI is not configured for environment: ${o}`);return n.redirectUri}function p(e,r){const t=c(r,"redirectUri");if(!e.redirectUris.includes(t))throw new TypeError("redirectUri is not configured for this OAuth application");return t}function d(e){if(!s.test(e))throw new TypeError("codeVerifier must be 43-128 RFC 7636 unreserved characters");return e}function f(e,r){const t=[...new Set((r||e.scopes).map(e=>String(e).trim()).filter(Boolean))];if(0===t.length)throw new TypeError("at least one OAuth scope is required");const o=t.filter(r=>!e.scopes.includes(r));if(o.length)throw new TypeError(`OAuth scopes are not configured: ${o.sort().join(", ")}`);return t}export function createOAuthAuthorizationRequest(t,o){const n=p(t,o?.redirectUri),i=f(t,o?.scopes),s=String(o?.state||r(32).toString("base64url")).trim();if(!s)throw new TypeError("state is required");const c=d(o?.codeVerifier||r(64).toString("base64url")),a=o?.ttlSeconds??600;if(!Number.isInteger(a)||a<=0)throw new TypeError("ttlSeconds must be a positive integer");const u=e("sha256").update(c,"ascii").digest("base64url"),h=new URL(t.endpoints.authorization);h.search=new URLSearchParams({response_type:"code",client_id:t.clientId,redirect_uri:n,scope:i.join(" "),state:s,code_challenge:u,code_challenge_method:"S256"}).toString();const w=o?.now??Math.floor(Date.now()/1e3);return{authorizationUrl:h.toString(),state:s,codeVerifier:c,redirectUri:n,expiresAt:w+a}}function w(e,r){let t;try{t=e?JSON.parse(e):void 0}catch{t=void 0}const o=t&&"object"==typeof t&&String(t.error||"").trim()||void 0,n=t&&"object"==typeof t&&(t.error_description||t.message||o)||e||"OAuth request failed";return new OAuthProtocolError(String(n),{status:r,error:o,details:t})}async function l(e,{method:r,form:t,accessToken:o,allowEmpty:n=!1}){const i={Accept:"application/json"};let s,c;t&&(s=new URLSearchParams(Object.entries(t).filter(([,e])=>null!=e)).toString(),i["Content-Type"]="application/x-www-form-urlencoded"),o&&(i.Authorization=`Bearer ${o}`);try{c=await fetch(e,{method:r,headers:i,body:s})}catch(e){throw new OAuthProtocolError(`OAuth request failed: ${e?.message||e}`)}const a=await c.text();if(!c.ok)throw w(a,c.status);if(!a){if(n)return;throw new OAuthProtocolError("OAuth response body is empty",{status:c.status})}let u;try{u=JSON.parse(a)}catch{throw new OAuthProtocolError("OAuth response is not valid JSON",{status:c.status})}if(!u||"object"!=typeof u||Array.isArray(u))throw new OAuthProtocolError("OAuth response is not an object",{status:c.status,details:u});if(u.error)throw w(a,c.status);return u}export async function exchangeOAuthAuthorizationCode(e,r){const t=String(r?.code||"").trim();if(!t)throw new TypeError("authorization code is required");const o=p(e,r?.redirectUri),n=d(r?.codeVerifier);return l(e.endpoints.token,{method:"POST",form:{grant_type:"authorization_code",client_id:e.clientId,code:t,redirect_uri:o,code_verifier:n}})}export async function refreshOAuthAccessToken(e,r){const t=String(r?.refreshToken||"").trim();if(!t)throw new TypeError("refreshToken is required");return l(e.endpoints.token,{method:"POST",form:{grant_type:"refresh_token",client_id:e.clientId,refresh_token:t,scope:r?.scopes?f(e,r.scopes).join(" "):void 0}})}export async function getOAuthUserInfo(e,r){const t=String(r?.accessToken||"").trim();if(!t)throw new TypeError("accessToken is required");return l(e.endpoints.userinfo,{method:"GET",accessToken:t})}export async function revokeOAuthToken(e,r){const t=String(r?.token||"").trim();if(!t)throw new TypeError("token is required");await l(e.endpoints.revoke,{method:"POST",form:{client_id:e.clientId,token:t,token_type_hint:r?.tokenTypeHint},allowEmpty:!0})}export async function completeOAuthAuthorization(e,r){const t=await exchangeOAuthAuthorizationCode(e,r),o=String(t.access_token||"").trim();if(!o)throw new OAuthProtocolError("OAuth token response is missing access_token",{details:t});return{tokens:t,user:await getOAuthUserInfo(e,{accessToken:o})}}export const oauthRuntime={loadConfig:loadOAuthRuntimeConfig,redirectUriForEnvironment:oauthRedirectUriForEnvironment,createAuthorizationRequest:createOAuthAuthorizationRequest,exchangeAuthorizationCode:exchangeOAuthAuthorizationCode,refreshAccessToken:refreshOAuthAccessToken,getUserInfo:getOAuthUserInfo,revokeToken:revokeOAuthToken,completeAuthorization:completeOAuthAuthorization};
package/dist/storage.js CHANGED
@@ -1 +1 @@
1
- import{readFile as e,stat as t}from"node:fs/promises";import o from"node:path";import{ApiError as r}from"./shared.js";import{authHeaders as n,refreshRuntimeConfig as i,runtimeConfig as a,shouldRefreshUserToken as s}from"./runtime.js";export const MAX_UPLOAD_FILE_BYTES=20971520;async function l(){return a({environmentOverrideEnv:"LICOS_STORAGE_SCOPE"})}function d(e,t,o){return`${e.baseUrl}/api/v1/studio/storage${t}?${function(e,t={}){const o=new URLSearchParams;o.set("projectId",e.projectId),o.set("scope",e.environment);for(const[e,r]of Object.entries(t))null!=r&&o.set(e,String(r));return o.toString()}(e,o)}`}async function c(e){const t=await e.text(),o=t?JSON.parse(t):null;if(!e.ok)throw new r(t||`platform storage API returned ${e.status}`,{status:e.status,details:o});if(!o||"object"!=typeof o)return o;if(void 0!==o.code&&0!==o.code||!1===o.success)throw new r(o.message||"platform storage API failed",{status:e.status,code:"number"==typeof o.code?o.code:void 0,details:o});return o.data}async function f(e,t,{params:o,body:a}={}){let f=await l();for(let r=0;r<2;r+=1)try{const r=await fetch(d(f,t,o),{method:e,headers:n(f),body:void 0===a?void 0:JSON.stringify(a)});return await c(r)}catch(e){if(0===r&&s(e)){f=await i(f);continue}throw e}throw new r("platform storage API failed")}function u(e){if(e>20971520)throw new RangeError("uploadFile input exceeds max size of 20971520 bytes")}export async function summary(){return f("GET","/summary")}export async function listFolders(){return await f("GET","/folders")||[]}export async function listEntries({folderId:e}={}){return f("GET","/entries",{params:{folderId:e}})}export async function listFiles(){return await f("GET","/files")||[]}export async function createFolder(e,{parentId:t}={}){return f("POST","/folders",{body:{folderName:e,parentId:t}})}export async function renameFolder(e,t){return f("PUT",`/folders/${encodeURIComponent(e)}`,{body:{folderName:t}})}export async function deleteFolder(e){await f("DELETE",`/folders/${encodeURIComponent(e)}`)}export async function uploadFile(a,d={}){const{blob:f,fileName:p}=await async function(r,n={}){if("string"==typeof r){u((await t(r)).size);const i=await e(r);return{blob:new Blob([i],{type:n.contentType||"application/octet-stream"}),fileName:n.fileName||o.basename(r)}}if(r instanceof Blob)return u(r.size),{blob:r,fileName:n.fileName||"file"};if(r instanceof Uint8Array||Buffer.isBuffer(r))return u(r.byteLength),{blob:new Blob([r],{type:n.contentType||"application/octet-stream"}),fileName:n.fileName||"file"};throw new TypeError("uploadFile input must be a file path, Blob, Buffer, or Uint8Array")}(a,d);let m=await l();for(let e=0;e<2;e+=1){const t=new FormData;t.set("projectId",m.projectId),t.set("scope",m.environment),d.folderId&&t.set("folderId",d.folderId),t.set("file",f,p);try{const e=await fetch(`${m.baseUrl}/api/v1/studio/storage/files/upload`,{method:"POST",headers:n(m,void 0),body:t});return await c(e)}catch(t){if(0===e&&s(t)){m=await i(m);continue}throw t}}throw new r("platform storage upload failed")}export async function renameFile(e,t){return f("PUT",`/files/${encodeURIComponent(e)}/rename`,{body:{fileName:t}})}export async function moveFile(e,{folderId:t}={}){return f("PUT",`/files/${encodeURIComponent(e)}/move`,{body:{folderId:t}})}export async function shareUrl(e,{expirySeconds:t}={}){return f("POST",`/files/${encodeURIComponent(e)}/share-url`,{body:{expirySeconds:t}})}export async function objectExists({fileId:e,objectKey:t}={}){return f("POST","/objects/exists",{body:{fileId:e,objectKey:t}})}export async function deleteFile(e){await f("DELETE",`/files/${encodeURIComponent(e)}`)}export const storage={summary:summary,listFolders:listFolders,listEntries:listEntries,listFiles:listFiles,createFolder:createFolder,renameFolder:renameFolder,deleteFolder:deleteFolder,uploadFile:uploadFile,renameFile:renameFile,moveFile:moveFile,shareUrl:shareUrl,objectExists:objectExists,deleteFile:deleteFile};
1
+ import{createReadStream as e}from"node:fs";import{readFile as t,stat as o}from"node:fs/promises";import n from"node:path";import{ApiError as r}from"./shared.js";import{authHeaders as a,refreshRuntimeConfig as i,runtimeConfig as s,shouldRefreshUserToken as l}from"./runtime.js";export const MAX_UPLOAD_FILE_BYTES=104857600;export const DIRECT_UPLOAD_THRESHOLD_BYTES=8388608;async function c(){return s({environmentOverrideEnv:"LICOS_STORAGE_SCOPE"})}function d(e,t,o){return`${e.baseUrl}/api/v1/studio/storage${t}?${function(e,t={}){const o=new URLSearchParams;o.set("projectId",e.projectId),o.set("scope",e.environment);for(const[e,n]of Object.entries(t))null!=n&&o.set(e,String(n));return o.toString()}(e,o)}`}async function f(e){const t=await e.text(),o=t?JSON.parse(t):null;if(!e.ok)throw new r(t||`platform storage API returned ${e.status}`,{status:e.status,details:o});if(!o||"object"!=typeof o)return o;if(void 0!==o.code&&0!==o.code||!1===o.success)throw new r(o.message||"platform storage API failed",{status:e.status,code:"number"==typeof o.code?o.code:void 0,details:o});return o.data}async function u(e,t,{params:o,body:n}={}){let s=await c();for(let r=0;r<2;r+=1)try{const r=await fetch(d(s,t,o),{method:e,headers:a(s),body:void 0===n?void 0:JSON.stringify(n)});return await f(r)}catch(e){if(0===r&&l(e)){s=await i(s);continue}throw e}throw new r("platform storage API failed")}function p(e){if(e>104857600)throw new RangeError("uploadFile input exceeds max size of 104857600 bytes")}export async function summary(){return u("GET","/summary")}export async function listFolders(){return await u("GET","/folders")||[]}export async function listEntries({folderId:e}={}){return u("GET","/entries",{params:{folderId:e}})}export async function listFiles(){return await u("GET","/files")||[]}export async function createFolder(e,{parentId:t}={}){return u("POST","/folders",{body:{folderName:e,parentId:t}})}export async function renameFolder(e,t){return u("PUT",`/folders/${encodeURIComponent(e)}`,{body:{folderName:t}})}export async function deleteFolder(e){await u("DELETE",`/folders/${encodeURIComponent(e)}`)}export async function uploadFile(s,d={}){const y=await async function(r,a={}){if("string"==typeof r){const i=await o(r);return p(i.size),{size:i.size,fileName:a.fileName||n.basename(r),contentType:a.contentType||"application/octet-stream",readAll:async()=>t(r),chunks:t=>e(r,{highWaterMark:t})}}if(r instanceof Blob)return p(r.size),{size:r.size,fileName:a.fileName||"file",contentType:a.contentType||r.type||"application/octet-stream",readAll:async()=>new Uint8Array(await r.arrayBuffer()),chunks:async function*(e){for(let t=0;t<r.size;t+=e)yield new Uint8Array(await r.slice(t,t+e).arrayBuffer())}};if(r instanceof Uint8Array||Buffer.isBuffer(r)){p(r.byteLength);const e=Buffer.from(r);return{size:e.byteLength,fileName:a.fileName||"file",contentType:a.contentType||"application/octet-stream",readAll:async()=>e,chunks:async function*(t){for(let o=0;o<e.byteLength;o+=t)yield e.subarray(o,Math.min(o+t,e.byteLength))}}}throw new TypeError("uploadFile input must be a file path, Blob, Buffer, or Uint8Array")}(s,d);if(y.size>8388608)return async function(e,t){const o=await u("POST","/files/upload/init",{body:{fileName:e.fileName,contentType:e.contentType,sizeBytes:e.size,folderId:t.folderId}}),n=o?.uploadId;if(!n)throw new r("platform storage upload init returned no uploadId");const a=Number(o.partSize||8388608);let i=0;try{for await(const t of e.chunks(a))i+=1,await m("PUT",`/files/upload/${encodeURIComponent(n)}/parts/${i}`,t);return await u("POST",`/files/upload/${encodeURIComponent(n)}/complete`,{body:{partCount:i}})}catch(e){try{await u("DELETE",`/files/upload/${encodeURIComponent(n)}`)}catch{}throw e}}(y,d);const w=await y.readAll(),h=new Blob([w],{type:y.contentType});let b=await c();for(let e=0;e<2;e+=1){const t=new FormData;t.set("projectId",b.projectId),t.set("scope",b.environment),d.folderId&&t.set("folderId",d.folderId),t.set("file",h,y.fileName);try{const e=await fetch(`${b.baseUrl}/api/v1/studio/storage/files/upload`,{method:"POST",headers:a(b,null),body:t});return await f(e)}catch(t){if(0===e&&l(t)){b=await i(b);continue}throw t}}throw new r("platform storage upload failed")}async function m(e,t,o){let n=await c();for(let r=0;r<2;r+=1)try{const r=await fetch(d(n,t),{method:e,headers:a(n,"application/octet-stream"),body:o});return await f(r)}catch(e){if(0===r&&l(e)){n=await i(n);continue}throw e}throw new r("platform storage upload failed")}export async function renameFile(e,t){return u("PUT",`/files/${encodeURIComponent(e)}/rename`,{body:{fileName:t}})}export async function moveFile(e,{folderId:t}={}){return u("PUT",`/files/${encodeURIComponent(e)}/move`,{body:{folderId:t}})}export async function shareUrl(e,{expirySeconds:t}={}){return u("POST",`/files/${encodeURIComponent(e)}/share-url`,{body:{expirySeconds:t}})}export async function objectExists({fileId:e,objectKey:t}={}){return u("POST","/objects/exists",{body:{fileId:e,objectKey:t}})}export async function deleteFile(e){await u("DELETE",`/files/${encodeURIComponent(e)}`)}export const storage={summary:summary,listFolders:listFolders,listEntries:listEntries,listFiles:listFiles,createFolder:createFolder,renameFolder:renameFolder,deleteFolder:deleteFolder,uploadFile:uploadFile,renameFile:renameFile,moveFile:moveFile,shareUrl:shareUrl,objectExists:objectExists,deleteFile:deleteFile};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kmlckj/licos-platform-sdk",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "LICOS platform SDK package shell for browser and Node runtimes",
5
5
  "author": "kmlckj",
6
6
  "type": "module",