@walkeros/server-store-gcs 3.4.2 → 4.0.0-next-1777463920154

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
@@ -28,7 +28,7 @@ built-in auth (ADC on Cloud Run / service account JWT elsewhere).
28
28
  "file": {
29
29
  "package": "@walkeros/server-transformer-file",
30
30
  "config": { "settings": { "prefix": "/static" } },
31
- "env": { "store": "$store:assets" }
31
+ "env": { "store": "$store.assets" }
32
32
  }
33
33
  }
34
34
  }
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,a={};((e,r)=>{for(var o in r)t(e,o,{get:r[o],enumerable:!0})})(a,{default:()=>g,storeGcsInit:()=>g}),module.exports=(e=a,((e,a,i,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of o(a))n.call(e,c)||c===i||t(e,c,{get:()=>a[c],enumerable:!(s=r(a,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=require("crypto"),s="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",c="https://oauth2.googleapis.com/token",u="https://www.googleapis.com/auth/devstorage.read_write",f=6e4;function p(e){let t;return async function(){return t&&Date.now()<t.expiresAt||(t=e?await async function(e){const t=Math.floor(Date.now()/1e3),r=function(e,t){const r=l(JSON.stringify({alg:"RS256",typ:"JWT"})),o=l(JSON.stringify(e)),n=(0,i.createSign)("RSA-SHA256");return n.update(`${r}.${o}`),`${r}.${o}.${n.sign(t,"base64url")}`}({iss:e.client_email,scope:u,aud:c,iat:t,exp:t+3600},e.private_key),o=await fetch(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${r}`});if(!o.ok)throw new Error(`Token exchange error: ${o.status}`);const n=await o.json();return{token:n.access_token,expiresAt:Date.now()+1e3*n.expires_in-f}}(e):await async function(){const e=await fetch(s,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const t=await e.json();return{token:t.access_token,expiresAt:Date.now()+1e3*t.expires_in-f}}()),t.token}}function l(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}var d="https://storage.googleapis.com";var g=e=>{const t=e.config.settings,r=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),o=p(function(e){if(e)return"string"==typeof e?JSON.parse(e):e}(t.credentials)),n=encodeURIComponent(t.bucket);function a(t){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(t))return r+t;e.logger.warn("Invalid key rejected",{key:t})}return{type:"gcs",config:e.config,async get(e){const t=a(e);if(t)try{const e=await o(),r=`${d}/download/storage/v1/b/${n}/o/${encodeURIComponent(t)}?alt=media`,a=await fetch(r,{headers:{Authorization:`Bearer ${e}`}});if(!a.ok)return;return Buffer.from(await a.arrayBuffer())}catch(e){return}},async set(e,t){const r=a(e);if(!r)return;const i=await o(),s=`${d}/upload/storage/v1/b/${n}/o?uploadType=media&name=${encodeURIComponent(r)}`;await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/octet-stream"},body:new Uint8Array(t)})},async delete(e){const t=a(e);if(t)try{const e=await o(),r=`${d}/storage/v1/b/${n}/o/${encodeURIComponent(t)}`;await fetch(r,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch(e){}}}};//# sourceMappingURL=index.js.map
1
+ "use strict";var e,t=Object.defineProperty,r=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,a={};((e,r)=>{for(var o in r)t(e,o,{get:r[o],enumerable:!0})})(a,{default:()=>g,storeGcsInit:()=>g}),module.exports=(e=a,((e,a,i,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of o(a))n.call(e,c)||c===i||t(e,c,{get:()=>a[c],enumerable:!(s=r(a,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var i=require("crypto"),s="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",c="https://oauth2.googleapis.com/token",u="https://www.googleapis.com/auth/devstorage.read_write",f=6e4;function p(e){let t;return async function(){return t&&Date.now()<t.expiresAt||(t=e?await async function(e){const t=Math.floor(Date.now()/1e3),r=function(e,t){const r=l(JSON.stringify({alg:"RS256",typ:"JWT"})),o=l(JSON.stringify(e)),n=(0,i.createSign)("RSA-SHA256");return n.update(`${r}.${o}`),`${r}.${o}.${n.sign(t,"base64url")}`}({iss:e.client_email,scope:u,aud:c,iat:t,exp:t+3600},e.private_key),o=await fetch(c,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${r}`});if(!o.ok)throw new Error(`Token exchange error: ${o.status}`);const n=await o.json();return{token:n.access_token,expiresAt:Date.now()+1e3*n.expires_in-f}}(e):await async function(){const e=await fetch(s,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!e.ok)throw new Error(`Metadata server error: ${e.status}`);const t=await e.json();return{token:t.access_token,expiresAt:Date.now()+1e3*t.expires_in-f}}()),t.token}}function l(e){return("string"==typeof e?Buffer.from(e):e).toString("base64url")}var d="https://storage.googleapis.com";var g=e=>{const t=e.config.settings,r=function(e){if(!e)return"";const t=e.replace(/^\/+|\/+$/g,"");return t?t+"/":""}(t.prefix),o=p(function(e){if(e)return"string"==typeof e?JSON.parse(e):e}(t.credentials)),n=encodeURIComponent(t.bucket);function a(t){if(function(e){return!(!e||e.startsWith("/")||e.startsWith("\\")||e.split(/[/\\]/).includes(".."))}(t))return r+t;e.logger.warn("Invalid key rejected",{key:t})}return{type:"gcs",config:e.config,async get(e){const t=a(e);if(t)try{const e=await o(),r=`${d}/download/storage/v1/b/${n}/o/${encodeURIComponent(t)}?alt=media`,a=await fetch(r,{headers:{Authorization:`Bearer ${e}`}});if(!a.ok)return;return Buffer.from(await a.arrayBuffer())}catch{return}},async set(e,t){const r=a(e);if(!r)return;const i=await o(),s=`${d}/upload/storage/v1/b/${n}/o?uploadType=media&name=${encodeURIComponent(r)}`;await fetch(s,{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/octet-stream"},body:new Uint8Array(t)})},async delete(e){const t=a(e);if(t)try{const e=await o(),r=`${d}/storage/v1/b/${n}/o/${encodeURIComponent(t)}`;await fetch(r,{method:"DELETE",headers:{Authorization:`Bearer ${e}`}})}catch{}}}};//# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/store.ts"],"sourcesContent":["export { storeGcsInit } from './store';\nexport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nexport { storeGcsInit as default } from './store';\n","import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { Store } from '@walkeros/core';\nimport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nimport { createTokenProvider } from './auth';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') return JSON.parse(credentials);\n return credentials;\n}\n\nexport const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as GcsStoreSettings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucket = encodeURIComponent(settings.bucket);\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n return {\n type: 'gcs',\n config: context.config as Store.Config<Store.Types<GcsStoreSettings>>,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: new Uint8Array(value as Buffer),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/auth.ts","../src/store.ts"],"sourcesContent":["export { storeGcsInit } from './store';\nexport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nexport { storeGcsInit as default } from './store';\n","import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { Store } from '@walkeros/core';\nimport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nimport { createTokenProvider } from './auth';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') return JSON.parse(credentials);\n return credentials;\n}\n\nexport const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as GcsStoreSettings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucket = encodeURIComponent(settings.bucket);\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n return {\n type: 'gcs',\n config: context.config as Store.Config<Store.Types<GcsStoreSettings>>,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: new Uint8Array(value as Buffer),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createSign as t}from"crypto";var e="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",a="https://oauth2.googleapis.com/token",n="https://www.googleapis.com/auth/devstorage.read_write",o=6e4;function r(r){let s;return async function(){return s&&Date.now()<s.expiresAt||(s=r?await async function(e){const r=Math.floor(Date.now()/1e3),s=function(e,a){const n=i(JSON.stringify({alg:"RS256",typ:"JWT"})),o=i(JSON.stringify(e)),r=t("RSA-SHA256");return r.update(`${n}.${o}`),`${n}.${o}.${r.sign(a,"base64url")}`}({iss:e.client_email,scope:n,aud:a,iat:r,exp:r+3600},e.private_key),c=await fetch(a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${s}`});if(!c.ok)throw new Error(`Token exchange error: ${c.status}`);const u=await c.json();return{token:u.access_token,expiresAt:Date.now()+1e3*u.expires_in-o}}(r):await async function(){const t=await fetch(e,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!t.ok)throw new Error(`Metadata server error: ${t.status}`);const a=await t.json();return{token:a.access_token,expiresAt:Date.now()+1e3*a.expires_in-o}}()),s.token}}function i(t){return("string"==typeof t?Buffer.from(t):t).toString("base64url")}var s="https://storage.googleapis.com";var c=t=>{const e=t.config.settings,a=function(t){if(!t)return"";const e=t.replace(/^\/+|\/+$/g,"");return e?e+"/":""}(e.prefix),n=r(function(t){if(t)return"string"==typeof t?JSON.parse(t):t}(e.credentials)),o=encodeURIComponent(e.bucket);function i(e){if(function(t){return!(!t||t.startsWith("/")||t.startsWith("\\")||t.split(/[/\\]/).includes(".."))}(e))return a+e;t.logger.warn("Invalid key rejected",{key:e})}return{type:"gcs",config:t.config,async get(t){const e=i(t);if(e)try{const t=await n(),a=`${s}/download/storage/v1/b/${o}/o/${encodeURIComponent(e)}?alt=media`,r=await fetch(a,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok)return;return Buffer.from(await r.arrayBuffer())}catch(t){return}},async set(t,e){const a=i(t);if(!a)return;const r=await n(),c=`${s}/upload/storage/v1/b/${o}/o?uploadType=media&name=${encodeURIComponent(a)}`;await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/octet-stream"},body:new Uint8Array(e)})},async delete(t){const e=i(t);if(e)try{const t=await n(),a=`${s}/storage/v1/b/${o}/o/${encodeURIComponent(e)}`;await fetch(a,{method:"DELETE",headers:{Authorization:`Bearer ${t}`}})}catch(t){}}}};export{c as default,c as storeGcsInit};//# sourceMappingURL=index.mjs.map
1
+ import{createSign as t}from"crypto";var e="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",a="https://oauth2.googleapis.com/token",n="https://www.googleapis.com/auth/devstorage.read_write",o=6e4;function r(r){let s;return async function(){return s&&Date.now()<s.expiresAt||(s=r?await async function(e){const r=Math.floor(Date.now()/1e3),s=function(e,a){const n=i(JSON.stringify({alg:"RS256",typ:"JWT"})),o=i(JSON.stringify(e)),r=t("RSA-SHA256");return r.update(`${n}.${o}`),`${n}.${o}.${r.sign(a,"base64url")}`}({iss:e.client_email,scope:n,aud:a,iat:r,exp:r+3600},e.private_key),c=await fetch(a,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${s}`});if(!c.ok)throw new Error(`Token exchange error: ${c.status}`);const u=await c.json();return{token:u.access_token,expiresAt:Date.now()+1e3*u.expires_in-o}}(r):await async function(){const t=await fetch(e,{headers:{"Metadata-Flavor":"Google"},signal:AbortSignal.timeout(5e3)});if(!t.ok)throw new Error(`Metadata server error: ${t.status}`);const a=await t.json();return{token:a.access_token,expiresAt:Date.now()+1e3*a.expires_in-o}}()),s.token}}function i(t){return("string"==typeof t?Buffer.from(t):t).toString("base64url")}var s="https://storage.googleapis.com";var c=t=>{const e=t.config.settings,a=function(t){if(!t)return"";const e=t.replace(/^\/+|\/+$/g,"");return e?e+"/":""}(e.prefix),n=r(function(t){if(t)return"string"==typeof t?JSON.parse(t):t}(e.credentials)),o=encodeURIComponent(e.bucket);function i(e){if(function(t){return!(!t||t.startsWith("/")||t.startsWith("\\")||t.split(/[/\\]/).includes(".."))}(e))return a+e;t.logger.warn("Invalid key rejected",{key:e})}return{type:"gcs",config:t.config,async get(t){const e=i(t);if(e)try{const t=await n(),a=`${s}/download/storage/v1/b/${o}/o/${encodeURIComponent(e)}?alt=media`,r=await fetch(a,{headers:{Authorization:`Bearer ${t}`}});if(!r.ok)return;return Buffer.from(await r.arrayBuffer())}catch{return}},async set(t,e){const a=i(t);if(!a)return;const r=await n(),c=`${s}/upload/storage/v1/b/${o}/o?uploadType=media&name=${encodeURIComponent(a)}`;await fetch(c,{method:"POST",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/octet-stream"},body:new Uint8Array(e)})},async delete(t){const e=i(t);if(e)try{const t=await n(),a=`${s}/storage/v1/b/${o}/o/${encodeURIComponent(e)}`;await fetch(a,{method:"DELETE",headers:{Authorization:`Bearer ${t}`}})}catch{}}}};export{c as default,c as storeGcsInit};//# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth.ts","../src/store.ts"],"sourcesContent":["import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { Store } from '@walkeros/core';\nimport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nimport { createTokenProvider } from './auth';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') return JSON.parse(credentials);\n return credentials;\n}\n\nexport const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as GcsStoreSettings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucket = encodeURIComponent(settings.bucket);\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n return {\n type: 'gcs',\n config: context.config as Store.Config<Store.Types<GcsStoreSettings>>,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: new Uint8Array(value as Buffer),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/auth.ts","../src/store.ts"],"sourcesContent":["import { createSign } from 'node:crypto';\nimport type { ServiceAccountCredentials } from './types';\n\nexport type TokenProvider = () => Promise<string>;\n\nconst METADATA_URL =\n 'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token';\nconst OAUTH_URL = 'https://oauth2.googleapis.com/token';\nconst SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write';\nconst REFRESH_MARGIN_MS = 60_000;\n\ninterface CachedToken {\n token: string;\n expiresAt: number;\n}\n\nexport function createTokenProvider(\n credentials?: ServiceAccountCredentials,\n): TokenProvider {\n let cached: CachedToken | undefined;\n\n return async function getToken(): Promise<string> {\n if (cached && Date.now() < cached.expiresAt) return cached.token;\n\n cached = credentials\n ? await fetchServiceAccountToken(credentials)\n : await fetchMetadataToken();\n\n return cached.token;\n };\n}\n\nasync function fetchMetadataToken(): Promise<CachedToken> {\n const res = await fetch(METADATA_URL, {\n headers: { 'Metadata-Flavor': 'Google' },\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`Metadata server error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nasync function fetchServiceAccountToken(\n creds: ServiceAccountCredentials,\n): Promise<CachedToken> {\n const now = Math.floor(Date.now() / 1000);\n const jwt = signJwt(\n {\n iss: creds.client_email,\n scope: SCOPE,\n aud: OAUTH_URL,\n iat: now,\n exp: now + 3600,\n },\n creds.private_key,\n );\n\n const res = await fetch(OAUTH_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,\n });\n if (!res.ok) throw new Error(`Token exchange error: ${res.status}`);\n\n const data = (await res.json()) as {\n access_token: string;\n expires_in: number;\n };\n return {\n token: data.access_token,\n expiresAt: Date.now() + data.expires_in * 1000 - REFRESH_MARGIN_MS,\n };\n}\n\nfunction base64url(input: string | Buffer): string {\n const buf = typeof input === 'string' ? Buffer.from(input) : input;\n return buf.toString('base64url');\n}\n\nfunction signJwt(payload: object, privateKey: string): string {\n const header = base64url(JSON.stringify({ alg: 'RS256', typ: 'JWT' }));\n const body = base64url(JSON.stringify(payload));\n const sign = createSign('RSA-SHA256');\n sign.update(`${header}.${body}`);\n return `${header}.${body}.${sign.sign(privateKey, 'base64url')}`;\n}\n","import type { Store } from '@walkeros/core';\nimport type { GcsStoreSettings, ServiceAccountCredentials } from './types';\nimport { createTokenProvider } from './auth';\n\nconst GCS_BASE = 'https://storage.googleapis.com';\n\nfunction isValidKey(key: string): boolean {\n if (!key || key.startsWith('/') || key.startsWith('\\\\')) return false;\n return !key.split(/[/\\\\]/).includes('..');\n}\n\nfunction normalizePrefix(prefix?: string): string {\n if (!prefix) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed ? trimmed + '/' : '';\n}\n\nfunction parseCredentials(\n credentials?: string | ServiceAccountCredentials,\n): ServiceAccountCredentials | undefined {\n if (!credentials) return undefined;\n if (typeof credentials === 'string') return JSON.parse(credentials);\n return credentials;\n}\n\nexport const storeGcsInit: Store.Init<Store.Types<GcsStoreSettings>> = (\n context,\n) => {\n const settings = context.config.settings as GcsStoreSettings;\n const prefix = normalizePrefix(settings.prefix);\n const creds = parseCredentials(settings.credentials);\n const getToken = createTokenProvider(creds);\n const bucket = encodeURIComponent(settings.bucket);\n\n function resolveKey(key: string): string | undefined {\n if (!isValidKey(key)) {\n context.logger.warn('Invalid key rejected', { key });\n return undefined;\n }\n return prefix + key;\n }\n\n return {\n type: 'gcs',\n config: context.config as Store.Config<Store.Types<GcsStoreSettings>>,\n\n async get(key: string): Promise<Buffer | undefined> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return undefined;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/download/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}?alt=media`;\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return undefined;\n return Buffer.from(await res.arrayBuffer());\n } catch {\n return undefined;\n }\n },\n\n async set(key: string, value: unknown): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n const token = await getToken();\n const url = `${GCS_BASE}/upload/storage/v1/b/${bucket}/o?uploadType=media&name=${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/octet-stream',\n },\n body: new Uint8Array(value as Buffer),\n });\n },\n\n async delete(key: string): Promise<void> {\n const gcsKey = resolveKey(key);\n if (!gcsKey) return;\n\n try {\n const token = await getToken();\n const url = `${GCS_BASE}/storage/v1/b/${bucket}/o/${encodeURIComponent(gcsKey)}`;\n await fetch(url, {\n method: 'DELETE',\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n /* GCS delete is idempotent */\n }\n },\n };\n};\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$meta": {
3
3
  "package": "@walkeros/server-store-gcs",
4
- "version": "3.4.2",
4
+ "version": "4.0.0-next-1777463920154",
5
5
  "type": "store",
6
6
  "platform": [
7
7
  "server"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walkeros/server-store-gcs",
3
3
  "description": "Google Cloud Storage for walkerOS server flows",
4
- "version": "3.4.2",
4
+ "version": "4.0.0-next-1777463920154",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -32,7 +32,7 @@
32
32
  "update": "npx npm-check-updates -u && npm update"
33
33
  },
34
34
  "dependencies": {
35
- "@walkeros/core": "3.4.2"
35
+ "@walkeros/core": "4.0.0-next-1777463920154"
36
36
  },
37
37
  "devDependencies": {},
38
38
  "repository": {