CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-google-cloud--storage

Cloud Storage Client Library for Node.js that provides comprehensive API for managing buckets, files, and metadata with authentication, streaming, and access control.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

access-control.mddocs/

Access Control

Google Cloud Storage provides two access control systems: Access Control Lists (ACLs) for fine-grained permissions and Identity and Access Management (IAM) for resource-level permissions.

ACL (Access Control Lists)

Acl Class

class Acl {
  constructor(options: AclOptions);
  
  // Properties
  pathPrefix: string;
  
  // Dynamic role accessors
  owners: AclRoleAccessors;
  readers: AclRoleAccessors;
  writers: AclRoleAccessors;
  
  // Core methods
  add(options: AddAclOptions): Promise<AddAclResponse>;
  get(options: GetAclOptions): Promise<GetAclResponse>;
  update(options: UpdateAclOptions): Promise<UpdateAclResponse>;
  remove(options: RemoveAclOptions): Promise<RemoveAclResponse>;
}

interface AclOptions {
  pathPrefix: string;
  request: (reqOpts: object, callback: Function) => void;
}

ACL Types and Interfaces

interface AclMetadata {
  entity?: string;
  role?: 'OWNER' | 'READER' | 'WRITER' | 'FULL_CONTROL';
  projectTeam?: {
    projectNumber?: string;
    team?: 'editors' | 'owners' | 'viewers';
  };
}

interface AccessControlObject {
  entity: string;
  role: string;
  projectTeam?: string;
}

// Entity types
type AclEntity = 
  | `user-${string}`           // user-email@example.com
  | `group-${string}`          // group-groupname@domain.com
  | `domain-${string}`         // domain-example.com
  | `project-editors-${string}` // project-editors-123456789
  | `project-owners-${string}`  // project-owners-123456789
  | `project-viewers-${string}` // project-viewers-123456789
  | 'allUsers'                 // All users (public)
  | 'allAuthenticatedUsers';   // All authenticated users

// Predefined ACL values
type PredefinedAcl = 
  | 'authenticatedRead'
  | 'bucketOwnerFullControl'
  | 'bucketOwnerRead'
  | 'private'
  | 'projectPrivate'
  | 'publicRead'
  | 'publicReadWrite';

ACL Operations

add(options: AddAclOptions): Promise<AddAclResponse>
get(options?: GetAclOptions): Promise<GetAclResponse>
update(options: UpdateAclOptions): Promise<UpdateAclResponse>
remove(options: RemoveAclOptions): Promise<RemoveAclResponse>

interface AddAclOptions {
  entity: string;
  role: string;
  generation?: number;
  userProject?: string;
}

interface GetAclOptions {
  entity?: string;
  generation?: number;
  userProject?: string;
}

interface UpdateAclOptions {
  entity: string;
  role: string;
  generation?: number;
  userProject?: string;
}

interface RemoveAclOptions {
  entity: string;
  generation?: number;
  userProject?: string;
}

type AddAclResponse = [AccessControlObject, unknown]; // [acl, apiResponse]
type GetAclResponse = [AccessControlObject[], unknown]; // [acls, apiResponse]
type UpdateAclResponse = [AccessControlObject, unknown]; // [acl, apiResponse]  
type RemoveAclResponse = [unknown]; // [apiResponse]

// Add ACL entry
await bucket.acl.add({
  entity: 'user-john@example.com',
  role: 'READER'
});

// Add group access
await file.acl.add({
  entity: 'group-developers@company.com',
  role: 'WRITER'
});

// Add domain access
await bucket.acl.add({
  entity: 'domain-example.com',
  role: 'READER'
});

// Add project team access
await bucket.acl.add({
  entity: 'project-editors-123456789',
  role: 'WRITER'
});

// Make public
await file.acl.add({
  entity: 'allUsers',
  role: 'READER'
});

// Get all ACL entries
const [acls] = await bucket.acl.get();
acls.forEach(acl => {
  console.log(`Entity: ${acl.entity}, Role: ${acl.role}`);
});

// Get specific entity ACL
const [acls] = await file.acl.get({
  entity: 'user-john@example.com'
});

// Update ACL entry
await bucket.acl.update({
  entity: 'user-john@example.com',
  role: 'OWNER'
});

// Remove ACL entry
await file.acl.remove({
  entity: 'user-john@example.com'
});

Role-based ACL Methods

Each role (owners, readers, writers) provides convenient methods:

interface AclRoleAccessors {
  addUser(entity: string): Promise<AddAclResponse>;
  deleteUser(entity: string): Promise<RemoveAclResponse>;
  addGroup(entity: string): Promise<AddAclResponse>;
  deleteGroup(entity: string): Promise<RemoveAclResponse>;
  addDomain(entity: string): Promise<AddAclResponse>;
  deleteDomain(entity: string): Promise<RemoveAclResponse>;
  addProject(entity: string): Promise<AddAclResponse>;
  deleteProject(entity: string): Promise<RemoveAclResponse>;
  addAllUsers(): Promise<AddAclResponse>;
  deleteAllUsers(): Promise<RemoveAclResponse>;
  addAllAuthenticatedUsers(): Promise<AddAclResponse>;
  deleteAllAuthenticatedUsers(): Promise<RemoveAclResponse>;
}

// Owners (OWNER role)
await bucket.acl.owners.addUser('owner@example.com');
await bucket.acl.owners.addGroup('owners@company.com');
await bucket.acl.owners.deleteUser('former-owner@example.com');

// Readers (READER role)  
await file.acl.readers.addUser('reader@example.com');
await file.acl.readers.addDomain('partner.com');
await file.acl.readers.addAllUsers(); // Make public read
await file.acl.readers.deleteAllUsers(); // Remove public access

// Writers (WRITER role)
await bucket.acl.writers.addUser('editor@example.com');
await bucket.acl.writers.addProject('project-editors-123456789');
await bucket.acl.writers.deleteGroup('old-editors@company.com');

// Authenticated users access
await file.acl.readers.addAllAuthenticatedUsers();
await file.acl.readers.deleteAllAuthenticatedUsers();

Bucket vs File ACLs

// Bucket ACLs control bucket-level permissions
const bucket = storage.bucket('my-bucket');

// Who can list files in the bucket
await bucket.acl.readers.addUser('lister@example.com');

// Who can add/delete files in the bucket
await bucket.acl.writers.addGroup('editors@company.com');

// Who has full control over the bucket
await bucket.acl.owners.addUser('admin@example.com');

// File ACLs control individual file permissions
const file = bucket.file('document.pdf');

// Who can read this specific file
await file.acl.readers.addUser('viewer@example.com');

// Who can overwrite this specific file
await file.acl.writers.addUser('editor@example.com');

IAM (Identity and Access Management)

Iam Class

class Iam {
  constructor(bucket: Bucket);
  
  // Methods
  getPolicy(options?: GetPolicyOptions): Promise<GetPolicyResponse>;
  setPolicy(policy: Policy, options?: SetPolicyOptions): Promise<SetPolicyResponse>;
  testIamPermissions(permissions: string | string[], options?: TestIamPermissionsOptions): Promise<TestIamPermissionsResponse>;
}

IAM Types

interface Policy {
  bindings?: PolicyBinding[];
  etag?: string;
  version?: number;
}

interface PolicyBinding {
  role?: string;
  members?: string[];
  condition?: Expr;
}

interface Expr {
  expression?: string;
  title?: string;
  description?: string;
  location?: string;
}

// Common IAM roles for Cloud Storage
type StorageRole = 
  | 'roles/storage.admin'
  | 'roles/storage.objectAdmin'
  | 'roles/storage.objectCreator'
  | 'roles/storage.objectViewer'
  | 'roles/storage.legacyBucketOwner'
  | 'roles/storage.legacyBucketReader'
  | 'roles/storage.legacyBucketWriter'
  | 'roles/storage.legacyObjectOwner'
  | 'roles/storage.legacyObjectReader';

// Member types
type IamMember = 
  | `user:${string}`                    // user:john@example.com
  | `group:${string}`                   // group:team@company.com
  | `serviceAccount:${string}`          // serviceAccount:service@project.iam.gserviceaccount.com
  | `domain:${string}`                  // domain:example.com
  | `projectEditor:${string}`           // projectEditor:project-id
  | `projectOwner:${string}`            // projectOwner:project-id
  | `projectViewer:${string}`           // projectViewer:project-id
  | 'allUsers'                          // All users
  | 'allAuthenticatedUsers';            // All authenticated users

IAM Policy Operations

getPolicy(options?: GetPolicyOptions): Promise<GetPolicyResponse>
setPolicy(policy: Policy, options?: SetPolicyOptions): Promise<SetPolicyResponse>
testIamPermissions(permissions: string | string[], options?: TestIamPermissionsOptions): Promise<TestIamPermissionsResponse>

interface GetPolicyOptions {
  userProject?: string;
  requestedPolicyVersion?: number;
}

interface SetPolicyOptions {
  userProject?: string;
}

interface TestIamPermissionsOptions {
  userProject?: string;
}

type GetPolicyResponse = [Policy, unknown]; // [policy, apiResponse]
type SetPolicyResponse = [Policy, unknown]; // [policy, apiResponse]
type TestIamPermissionsResponse = [string[], unknown]; // [permissions, apiResponse]

// Get current IAM policy
const [policy] = await bucket.iam.getPolicy();
console.log('Current policy:', policy);

// Create a new policy
const newPolicy: Policy = {
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: [
        'user:viewer@example.com',
        'group:readers@company.com'
      ]
    },
    {
      role: 'roles/storage.objectAdmin',
      members: [
        'user:admin@example.com',
        'serviceAccount:app@project.iam.gserviceaccount.com'
      ]
    }
  ]
};

// Set the policy
await bucket.iam.setPolicy(newPolicy);

// Test permissions
const [permissions] = await bucket.iam.testIamPermissions([
  'storage.objects.get',
  'storage.objects.create',
  'storage.objects.delete'
]);

console.log('Allowed permissions:', permissions);

IAM Policy Management

// Add a binding to existing policy
const [currentPolicy] = await bucket.iam.getPolicy();

// Find existing binding or create new one
let binding = currentPolicy.bindings?.find(b => b.role === 'roles/storage.objectViewer');
if (!binding) {
  binding = {
    role: 'roles/storage.objectViewer',
    members: []
  };
  currentPolicy.bindings = currentPolicy.bindings || [];
  currentPolicy.bindings.push(binding);
}

// Add member to binding
if (!binding.members?.includes('user:newuser@example.com')) {
  binding.members = binding.members || [];
  binding.members.push('user:newuser@example.com');
}

// Update policy
await bucket.iam.setPolicy(currentPolicy);

// Remove a member
const updatedBindings = currentPolicy.bindings?.map(binding => {
  if (binding.role === 'roles/storage.objectViewer') {
    binding.members = binding.members?.filter(member => 
      member !== 'user:olduser@example.com'
    );
  }
  return binding;
}).filter(binding => binding.members && binding.members.length > 0);

await bucket.iam.setPolicy({
  ...currentPolicy,
  bindings: updatedBindings
});

Conditional IAM Policies

// Policy with conditions
const conditionalPolicy: Policy = {
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['user:contractor@example.com'],
      condition: {
        title: 'Temporary Access',
        description: 'Access expires at end of project',
        expression: 'request.time < timestamp("2024-12-31T23:59:59Z")'
      }
    },
    {
      role: 'roles/storage.objectCreator',
      members: ['user:uploader@example.com'],
      condition: {
        title: 'Upload to specific prefix',
        description: 'Can only upload to uploads/ prefix',
        expression: 'resource.name.startsWith("projects/_/buckets/my-bucket/objects/uploads/")'
      }
    }
  ]
};

await bucket.iam.setPolicy(conditionalPolicy);

// Time-based access
const timeBasedPolicy: Policy = {
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['user:timeuser@example.com'],
      condition: {
        expression: `
          request.time.getHours() >= 9 && 
          request.time.getHours() <= 17 &&
          request.time.getDayOfWeek() >= 2 && 
          request.time.getDayOfWeek() <= 6
        `.replace(/\s+/g, ' ').trim()
      }
    }
  ]
};

Common Access Control Patterns

Public Access

// Make bucket public for reading
await bucket.acl.readers.addAllUsers();

// Make specific file public
await file.acl.readers.addAllUsers();

// Public read using IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['allUsers']
    }
  ]
});

// Using predefined ACLs
await bucket.upload('/local/file.txt', {
  predefinedAcl: 'publicRead'
});

Authenticated Users Only

// Authenticated users can read via ACL
await bucket.acl.readers.addAllAuthenticatedUsers();

// Authenticated users via IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer', 
      members: ['allAuthenticatedUsers']
    }
  ]
});

Team Access

// Team access via groups
await bucket.acl.readers.addGroup('team-viewers@company.com');
await bucket.acl.writers.addGroup('team-editors@company.com');
await bucket.acl.owners.addGroup('team-admins@company.com');

// Team access via IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: [
        'group:team-viewers@company.com',
        'group:all-employees@company.com'
      ]
    },
    {
      role: 'roles/storage.objectCreator',
      members: [
        'group:team-editors@company.com'
      ]
    },
    {
      role: 'roles/storage.admin',
      members: [
        'group:team-admins@company.com'
      ]
    }
  ]
});

Service Account Access

// Service account access via ACL
await bucket.acl.writers.addUser('service@project.iam.gserviceaccount.com');

// Service account access via IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectAdmin',
      members: [
        'serviceAccount:app-backend@project.iam.gserviceaccount.com',
        'serviceAccount:data-processor@project.iam.gserviceaccount.com'
      ]
    }
  ]
});

Project Team Access

// Project team access via ACL
await bucket.acl.readers.addProject('project-viewers-123456789');
await bucket.acl.writers.addProject('project-editors-123456789');

// Project team access via IAM  
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['projectViewer:my-project-id']
    },
    {
      role: 'roles/storage.objectCreator', 
      members: ['projectEditor:my-project-id']
    },
    {
      role: 'roles/storage.admin',
      members: ['projectOwner:my-project-id']
    }
  ]
});

ACL vs IAM Comparison

When to Use ACLs

// Use ACLs for:
// - Fine-grained, object-level permissions
// - Simple read/write/owner permissions
// - Legacy applications expecting ACL semantics
// - When you need different permissions per file

// Example: Different access per file
await publicFile.acl.readers.addAllUsers();
await privateFile.acl.readers.addUser('specific-user@example.com');
await internalFile.acl.readers.addGroup('internal@company.com');

When to Use IAM

// Use IAM for:
// - Resource-level permissions
// - Complex conditions and constraints
// - Integration with Google Cloud IAM
// - Centralized access management

// Example: Bucket-wide policies
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['group:all-employees@company.com']
    },
    {
      role: 'roles/storage.objectCreator',
      members: ['group:content-creators@company.com'],
      condition: {
        expression: 'request.time.getHours() >= 9 && request.time.getHours() <= 17'
      }
    }
  ]
});

Error Handling

import { ApiError } from '@google-cloud/storage';

try {
  await bucket.acl.add({
    entity: 'user-invalid@example.com',
    role: 'READER'
  });
} catch (error) {
  if (error instanceof ApiError) {
    if (error.code === 400) {
      console.log('Invalid entity or role');
    } else if (error.code === 403) {
      console.log('Permission denied');
    } else {
      console.error(`ACL Error ${error.code}: ${error.message}`);
    }
  }
}

// Test permissions before operations
const [permissions] = await bucket.iam.testIamPermissions([
  'storage.objects.create'
]);

if (permissions.includes('storage.objects.create')) {
  // User can create objects
  await bucket.upload('/local/file.txt');
} else {
  console.log('User cannot create objects in this bucket');
}

Callback Support

// Promise pattern (recommended)
const [acls] = await bucket.acl.get();

// Callback pattern
bucket.acl.get((err, acls, apiResponse) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('ACL entries:', acls);
});

// Callback types
interface GetAclCallback {
  (err: Error | null, acls?: AccessControlObject[], apiResponse?: unknown): void;
}

interface AddAclCallback {
  (err: Error | null, acl?: AccessControlObject, apiResponse?: unknown): void;
}

interface GetPolicyCallback {
  (err: Error | null, policy?: Policy, apiResponse?: unknown): void;
}

interface SetPolicyCallback {
  (err: Error | null, policy?: Policy, apiResponse?: unknown): void;
}

docs

access-control.md

authentication.md

bucket-operations.md

file-operations.md

index.md

notifications.md

storage-client.md

transfer-manager.md

utilities.md

tile.json