From 09bf376c2207a99bfca4ba852b94a5f716396ea0 Mon Sep 17 00:00:00 2001 From: Blayne Chard Date: Tue, 10 Oct 2023 15:55:58 +1300 Subject: [PATCH] feat(fs): support providing credentials for both read and read-write --- packages/fs-aws/src/credentials.ts | 22 +++++++++++++++------- packages/fs-aws/src/fs.s3.ts | 12 ++++++------ packages/fs/src/flags.ts | 2 ++ packages/fs/src/provider.ts | 12 ++++++++++-- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/fs-aws/src/credentials.ts b/packages/fs-aws/src/credentials.ts index b5cfeecf..936994af 100644 --- a/packages/fs-aws/src/credentials.ts +++ b/packages/fs-aws/src/credentials.ts @@ -1,6 +1,6 @@ import { S3Client } from '@aws-sdk/client-s3'; import { fromTemporaryCredentials } from '@aws-sdk/credential-providers'; -import { FileSystem, FileSystemProvider } from '@chunkd/fs'; +import { FileSystem, FileSystemProvider, Flag } from '@chunkd/fs'; import { FsAwsS3 } from './fs.s3.js'; import { AwsCredentialConfig, AwsCredentialProvider } from './types.js'; @@ -18,6 +18,12 @@ export function validateConfig(cfg: AwsCredentialProvider, loc: URL): AwsCredent return cfg; } +function credentialsMatch(cfg: AwsCredentialConfig, href: string, flag: Flag): boolean { + if (!href.startsWith(cfg.prefix)) return false; + if (flag === 'rw' && cfg.flags === 'r') return false; + return true; +} + export class FsConfigFetcher { loc: URL; fs: FileSystem; @@ -39,11 +45,11 @@ export class FsConfigFetcher { return this._config; } - async findCredentials(loc: URL): Promise { + async findCredentials(loc: URL, flag: Flag): Promise { const href = loc.href; const cfg = await this.config; for (const credentials of cfg.prefixes) { - if (href.startsWith(credentials.prefix)) return credentials; + if (credentialsMatch(credentials, href, flag)) return credentials; } return null; } @@ -147,21 +153,23 @@ export class AwsS3CredentialProvider implements FileSystemProvider { } /** Look up the credentials for a path */ - async findCredentials(loc: URL): Promise { + async findCredentials(loc: URL, flag: Flag): Promise { const href = loc.href; for (const cfg of this.configs) { if ('findCredentials' in cfg) { - const credentials = await cfg.findCredentials(loc); + const credentials = await cfg.findCredentials(loc, flag); if (credentials) return credentials; } else if (href.startsWith(cfg.prefix)) { + // If we need write credentials but these credentials only provide read skip it + if (flag === 'rw' && cfg.flags === 'r') continue; return cfg; } } return null; } - async find(path: URL): Promise { - const cs = await this.findCredentials(path); + async find(loc: URL, flag: Flag): Promise { + const cs = await this.findCredentials(loc, flag); if (cs == null) return null; const cacheKey = `${cs.roleArn}__${cs.externalId}__${cs.roleSessionDuration}`; diff --git a/packages/fs-aws/src/fs.s3.ts b/packages/fs-aws/src/fs.s3.ts index bc7588de..ce095307 100644 --- a/packages/fs-aws/src/fs.s3.ts +++ b/packages/fs-aws/src/fs.s3.ts @@ -130,7 +130,7 @@ export class FsAwsS3 implements FileSystem { const ce = toFsError(e, `Failed to list: "${loc}"`, loc, 'list', this); if (this.credentials != null && ce.code === 403) { - const newFs = await this.credentials.find(loc); + const newFs = await this.credentials.find(loc, 'r'); if (newFs) { yield* newFs.details(loc, opts); return; @@ -154,7 +154,7 @@ export class FsAwsS3 implements FileSystem { } catch (e) { const ce = toFsError(e, `Failed to read: "${loc}"`, loc, 'read', this); if (this.credentials != null && ce.code === 403) { - const newFs = await this.credentials.find(loc); + const newFs = await this.credentials.find(loc, 'r'); if (newFs) return newFs.read(loc); } throw ce; @@ -185,7 +185,7 @@ export class FsAwsS3 implements FileSystem { } catch (e) { const ce = toFsError(e, `Failed to write to "${loc}"`, loc, 'write', this); if (ce.code === 403) { - const newFs = await this.credentials.find(testPath); + const newFs = await this.credentials.find(testPath, 'rw'); if (newFs) return newFs; } throw ce; @@ -222,7 +222,7 @@ export class FsAwsS3 implements FileSystem { } catch (e) { const ce = toFsError(e, `Failed to write: "${loc}"`, loc, 'write', this); if (this.credentials != null && ce.code === 403) { - const newFs = await this.credentials.find(loc); + const newFs = await this.credentials.find(loc, 'rw'); if (newFs) return newFs.write(loc, buf, ctx); } throw ce; @@ -243,7 +243,7 @@ export class FsAwsS3 implements FileSystem { const ce = toFsError(e, `Failed to delete: "${loc}"`, loc, 'delete', this); if (ce.code === 404) return; if (this.credentials != null && ce.code === 403) { - const newFs = await this.credentials.find(loc); + const newFs = await this.credentials.find(loc, 'rw'); if (newFs) return newFs.delete(loc); } throw ce; @@ -297,7 +297,7 @@ export class FsAwsS3 implements FileSystem { if (ce.code === 404) return null; if (this.credentials != null && ce.code === 403) { - const newFs = await this.credentials.find(loc); + const newFs = await this.credentials.find(loc, 'r'); if (newFs) return newFs.head(loc); } throw ce; diff --git a/packages/fs/src/flags.ts b/packages/fs/src/flags.ts index 6f5dd1dc..24a1d1e3 100644 --- a/packages/fs/src/flags.ts +++ b/packages/fs/src/flags.ts @@ -1,3 +1,5 @@ export type Flag = FlagRead | FlagReadWrite; +/** Ability to read from a location */ export type FlagRead = 'r'; +/** Ability to read and write to a location */ export type FlagReadWrite = 'rw'; diff --git a/packages/fs/src/provider.ts b/packages/fs/src/provider.ts index 38ddfe08..89de7fe5 100644 --- a/packages/fs/src/provider.ts +++ b/packages/fs/src/provider.ts @@ -1,6 +1,14 @@ import { FileSystem } from './file.system.js'; +import { Flag } from './flags.js'; export interface FileSystemProvider { - /** find a file system for a given prefix */ - find(prefix: URL): Promise; + /** + * find a file system for a given prefix and permissions + * + * @param prefix location to search for + * @param flag Permissions required (Read or ReadWrite) + * + * @returns File System with the required permission, null otherwise + */ + find(prefix: URL, flag: Flag): Promise; }