mirror of
https://github.com/Nezumi-2711/google-drive-s3.git
synced 2026-09-22 20:01:19 +00:00
629 lines
22 KiB
YAML
629 lines
22 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: Google Drive S3 API
|
|
version: 1.0.0
|
|
description: |
|
|
An intentionally small S3-compatible object-storage API backed by Google Drive.
|
|
Authenticate every non-public request with AWS Signature Version 4. Browser clients
|
|
should receive short-lived presigned URLs from a backend-for-frontend, never credentials.
|
|
servers:
|
|
- url: https://{worker-host}
|
|
variables:
|
|
worker-host:
|
|
default: your-worker.example.workers.dev
|
|
security:
|
|
- sigv4: []
|
|
tags:
|
|
- name: Objects
|
|
- name: Buckets
|
|
- name: Multipart uploads
|
|
- name: Dashboard Auth
|
|
paths:
|
|
/auth/login:
|
|
post:
|
|
tags: [Dashboard Auth]
|
|
operationId: authLogin
|
|
summary: Login to dashboard
|
|
description: Authenticate with SHA-256 hashed dashboard password.
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/LoginRequest' }
|
|
responses:
|
|
'200':
|
|
description: Login successful.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/LoginResponse' }
|
|
'400':
|
|
description: Invalid request.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
'401':
|
|
description: Invalid password.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
'429':
|
|
description: Too many failed attempts.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
'503':
|
|
description: Dashboard authentication not configured.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
/auth/session:
|
|
get:
|
|
tags: [Dashboard Auth]
|
|
operationId: authSession
|
|
summary: Verify session token
|
|
security:
|
|
- bearerAuth: []
|
|
responses:
|
|
'200':
|
|
description: Session is valid.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
valid: { type: boolean, example: true }
|
|
'401':
|
|
description: Invalid or expired session.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
'503':
|
|
description: Dashboard authentication not configured.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
/auth/logout:
|
|
post:
|
|
tags: [Dashboard Auth]
|
|
operationId: authLogout
|
|
summary: Logout of dashboard session
|
|
security:
|
|
- bearerAuth: []
|
|
responses:
|
|
'204':
|
|
description: Successfully logged out.
|
|
'503':
|
|
description: Dashboard authentication not configured.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
/api/status:
|
|
get:
|
|
tags: [Dashboard Status]
|
|
operationId: getGatewayStatus
|
|
summary: Get gateway and storage health status
|
|
security:
|
|
- bearerAuth: []
|
|
responses:
|
|
'200':
|
|
description: Gateway status and Drive account/quota overview.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/GatewayStatus' }
|
|
'401':
|
|
description: Invalid or expired session.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
'503':
|
|
description: Dashboard authentication not configured.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
/api/buckets:
|
|
get:
|
|
tags: [Dashboard Status]
|
|
operationId: getBucketStats
|
|
summary: Get bucket list and object metrics
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- name: refresh
|
|
in: query
|
|
description: Set to 1 to bypass KV cache.
|
|
required: false
|
|
schema: { type: string, example: "1" }
|
|
responses:
|
|
'200':
|
|
description: Bucket statistics and aggregate totals.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/BucketStats' }
|
|
'401':
|
|
description: Invalid or expired session.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
'503':
|
|
description: Dashboard authentication not configured.
|
|
content:
|
|
application/json:
|
|
schema: { $ref: '#/components/schemas/AuthError' }
|
|
/{bucket}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/Bucket'
|
|
get:
|
|
tags: [Buckets]
|
|
operationId: listObjects
|
|
summary: List objects in a bucket
|
|
description: List objects recursively, or use `delimiter=/` to return immediate folders as `CommonPrefixes`.
|
|
parameters:
|
|
- $ref: '#/components/parameters/Prefix'
|
|
- $ref: '#/components/parameters/Delimiter'
|
|
responses:
|
|
'200':
|
|
description: Object listing.
|
|
content:
|
|
application/xml:
|
|
schema: { $ref: '#/components/schemas/ListBucketResult' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'405': { $ref: '#/components/responses/MethodNotAllowed' }
|
|
head:
|
|
tags: [Buckets]
|
|
operationId: headBucket
|
|
summary: Check bucket access
|
|
responses:
|
|
'200': { description: The bucket is configured and accessible. }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'405': { $ref: '#/components/responses/MethodNotAllowed' }
|
|
/{bucket}?uploads:
|
|
parameters:
|
|
- $ref: '#/components/parameters/Bucket'
|
|
get:
|
|
tags: [Multipart uploads]
|
|
operationId: listMultipartUploads
|
|
summary: List multipart uploads
|
|
description: Always returns an empty list; active multipart uploads cannot be enumerated.
|
|
responses:
|
|
'200':
|
|
description: Empty multipart upload listing.
|
|
content:
|
|
application/xml:
|
|
schema: { $ref: '#/components/schemas/ListMultipartUploadsResult' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
/{bucket}/{key}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/Bucket'
|
|
- $ref: '#/components/parameters/Key'
|
|
put:
|
|
tags: [Objects]
|
|
operationId: putObject
|
|
summary: Create or replace an object
|
|
parameters:
|
|
- $ref: '#/components/parameters/ContentType'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'*/*':
|
|
schema: { type: string, format: binary }
|
|
responses:
|
|
'200':
|
|
description: Object uploaded.
|
|
headers:
|
|
ETag: { $ref: '#/components/headers/ETag' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'501': { $ref: '#/components/responses/NotImplemented' }
|
|
get:
|
|
tags: [Objects]
|
|
operationId: getObject
|
|
summary: Download an object
|
|
parameters:
|
|
- name: Range
|
|
in: header
|
|
schema: { type: string, example: bytes=0-1023 }
|
|
responses:
|
|
'200':
|
|
description: Full object data.
|
|
headers:
|
|
ETag: { $ref: '#/components/headers/ETag' }
|
|
Content-Length: { $ref: '#/components/headers/ContentLength' }
|
|
Last-Modified: { $ref: '#/components/headers/LastModified' }
|
|
Accept-Ranges: { schema: { type: string, example: bytes } }
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'206':
|
|
description: Requested byte range.
|
|
headers:
|
|
Content-Range: { schema: { type: string, example: bytes 0-1023/4096 } }
|
|
Content-Length: { $ref: '#/components/headers/ContentLength' }
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchKey' }
|
|
head:
|
|
tags: [Objects]
|
|
operationId: headObject
|
|
summary: Get object metadata
|
|
responses:
|
|
'200':
|
|
description: Object metadata in headers.
|
|
headers:
|
|
ETag: { $ref: '#/components/headers/ETag' }
|
|
Content-Length: { $ref: '#/components/headers/ContentLength' }
|
|
Last-Modified: { $ref: '#/components/headers/LastModified' }
|
|
Accept-Ranges: { schema: { type: string, example: bytes } }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchKey' }
|
|
delete:
|
|
tags: [Objects]
|
|
operationId: deleteObject
|
|
summary: Delete an object
|
|
responses:
|
|
'204': { description: Object deleted. }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchKey' }
|
|
/{bucket}/{key}?uploads:
|
|
parameters:
|
|
- $ref: '#/components/parameters/Bucket'
|
|
- $ref: '#/components/parameters/Key'
|
|
post:
|
|
tags: [Multipart uploads]
|
|
operationId: createMultipartUpload
|
|
summary: Initiate a multipart upload
|
|
parameters:
|
|
- $ref: '#/components/parameters/ContentType'
|
|
responses:
|
|
'200':
|
|
description: Upload initiated.
|
|
content:
|
|
application/xml:
|
|
schema: { $ref: '#/components/schemas/InitiateMultipartUploadResult' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'501': { $ref: '#/components/responses/NotImplemented' }
|
|
/{bucket}/{key}?uploadId={uploadId}&partNumber={partNumber}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/Bucket'
|
|
- $ref: '#/components/parameters/Key'
|
|
- $ref: '#/components/parameters/UploadId'
|
|
- $ref: '#/components/parameters/PartNumber'
|
|
put:
|
|
tags: [Multipart uploads]
|
|
operationId: uploadPart
|
|
summary: Upload one multipart part
|
|
description: Parts must arrive strictly sequentially beginning with part number 1.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/octet-stream:
|
|
schema: { type: string, format: binary }
|
|
responses:
|
|
'200':
|
|
description: Part accepted.
|
|
headers:
|
|
ETag: { $ref: '#/components/headers/ETag' }
|
|
'400': { $ref: '#/components/responses/InvalidArgument' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchUpload' }
|
|
'503': { $ref: '#/components/responses/SlowDown' }
|
|
/{bucket}/{key}?uploadId={uploadId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/Bucket'
|
|
- $ref: '#/components/parameters/Key'
|
|
- $ref: '#/components/parameters/UploadId'
|
|
post:
|
|
tags: [Multipart uploads]
|
|
operationId: completeMultipartUpload
|
|
summary: Complete a multipart upload
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/xml:
|
|
schema: { $ref: '#/components/schemas/CompleteMultipartUpload' }
|
|
responses:
|
|
'200':
|
|
description: Upload completed.
|
|
content:
|
|
application/xml:
|
|
schema: { $ref: '#/components/schemas/CompleteMultipartUploadResult' }
|
|
'400': { $ref: '#/components/responses/InvalidArgument' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchUpload' }
|
|
get:
|
|
tags: [Multipart uploads]
|
|
operationId: listParts
|
|
summary: List uploaded parts
|
|
parameters:
|
|
- name: part-number-marker
|
|
in: query
|
|
schema: { type: integer, minimum: 0, default: 0 }
|
|
- name: max-parts
|
|
in: query
|
|
schema: { type: integer, minimum: 1, maximum: 1000, default: 1000 }
|
|
responses:
|
|
'200':
|
|
description: Current part list.
|
|
content:
|
|
application/xml:
|
|
schema: { $ref: '#/components/schemas/ListPartsResult' }
|
|
'400': { $ref: '#/components/responses/InvalidArgument' }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchUpload' }
|
|
delete:
|
|
tags: [Multipart uploads]
|
|
operationId: abortMultipartUpload
|
|
summary: Abort a multipart upload
|
|
responses:
|
|
'204': { description: Upload aborted. }
|
|
'403': { $ref: '#/components/responses/AccessDenied' }
|
|
'404': { $ref: '#/components/responses/NoSuchUpload' }
|
|
components:
|
|
securitySchemes:
|
|
sigv4:
|
|
type: apiKey
|
|
in: header
|
|
name: Authorization
|
|
description: AWS Signature Version 4 header authentication or equivalent `X-Amz-*` presigned query parameters.
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
description: Session token returned from `/auth/login`.
|
|
parameters:
|
|
Bucket:
|
|
name: bucket
|
|
in: path
|
|
required: true
|
|
schema: { type: string }
|
|
Key:
|
|
name: key
|
|
in: path
|
|
required: true
|
|
schema: { type: string }
|
|
Prefix:
|
|
name: prefix
|
|
in: query
|
|
schema: { type: string, default: '' }
|
|
Delimiter:
|
|
name: delimiter
|
|
in: query
|
|
schema: { type: string, example: / }
|
|
UploadId:
|
|
name: uploadId
|
|
in: query
|
|
required: true
|
|
schema: { type: string }
|
|
PartNumber:
|
|
name: partNumber
|
|
in: query
|
|
required: true
|
|
schema: { type: integer, minimum: 1, maximum: 10000 }
|
|
ContentType:
|
|
name: Content-Type
|
|
in: header
|
|
schema: { type: string, example: application/octet-stream }
|
|
headers:
|
|
ETag:
|
|
schema: { type: string, example: '"d41d8cd98f00b204e9800998ecf8427e"' }
|
|
ContentLength:
|
|
schema: { type: integer, minimum: 0 }
|
|
LastModified:
|
|
schema: { type: string, format: date-time }
|
|
responses:
|
|
AccessDenied:
|
|
description: Authentication failed, the access key is not recognized, the presigned URL expired, or bucket access is denied.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
NoSuchKey:
|
|
description: The object does not exist.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
NoSuchUpload:
|
|
description: The multipart upload does not exist.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
InvalidArgument:
|
|
description: A required parameter or body is invalid.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
NotImplemented:
|
|
description: This S3 operation is not supported.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
SlowDown:
|
|
description: Parts are out of sequence or the upload is busy; retry later.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
MethodNotAllowed:
|
|
description: The method/path combination is unsupported.
|
|
content: { application/xml: { schema: { $ref: '#/components/schemas/S3Error' } } }
|
|
schemas:
|
|
LoginRequest:
|
|
type: object
|
|
required: [passwordHash]
|
|
properties:
|
|
passwordHash:
|
|
type: string
|
|
description: SHA-256 hash of the dashboard password in lowercase hex.
|
|
example: ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f
|
|
LoginResponse:
|
|
type: object
|
|
required: [token, expiresIn]
|
|
properties:
|
|
token:
|
|
type: string
|
|
description: Session token.
|
|
expiresIn:
|
|
type: integer
|
|
description: Token TTL in seconds.
|
|
example: 43200
|
|
GatewayStatus:
|
|
type: object
|
|
required: [gateway, drive, checkedAt]
|
|
properties:
|
|
gateway:
|
|
type: object
|
|
required: [status, region, multipartEnabled, etagStyle, docsEnabled, buckets, publicReadBuckets, corsOrigins, credentials]
|
|
properties:
|
|
status: { type: string, enum: [ok, degraded] }
|
|
region: { type: string, example: auto }
|
|
multipartEnabled: { type: boolean }
|
|
etagStyle: { type: string, enum: [md5, multipart] }
|
|
docsEnabled: { type: boolean }
|
|
buckets: { type: array, items: { type: string } }
|
|
publicReadBuckets: { type: array, items: { type: string } }
|
|
corsOrigins: { type: array, items: { type: string } }
|
|
credentials:
|
|
type: object
|
|
required: [s3Keys, googleOAuth, dashboardPassword]
|
|
properties:
|
|
s3Keys: { type: boolean }
|
|
googleOAuth: { type: boolean }
|
|
dashboardPassword: { type: boolean }
|
|
drive:
|
|
type: object
|
|
required: [connected, account, quota, error]
|
|
properties:
|
|
connected: { type: boolean }
|
|
account:
|
|
type: object
|
|
nullable: true
|
|
properties:
|
|
email: { type: string, nullable: true }
|
|
displayName: { type: string, nullable: true }
|
|
quota:
|
|
type: object
|
|
nullable: true
|
|
properties:
|
|
limit: { type: integer, nullable: true }
|
|
usage: { type: integer }
|
|
usageInDrive: { type: integer }
|
|
usageInDriveTrash: { type: integer }
|
|
free: { type: integer, nullable: true }
|
|
percentUsed: { type: number, nullable: true }
|
|
error: { type: string, nullable: true }
|
|
checkedAt:
|
|
type: string
|
|
format: date-time
|
|
BucketStats:
|
|
type: object
|
|
required: [buckets, totals, cachedAt]
|
|
properties:
|
|
buckets:
|
|
type: array
|
|
items:
|
|
type: object
|
|
required: [name, objectCount, totalSize, lastModified, truncated, publicRead, error]
|
|
properties:
|
|
name: { type: string }
|
|
objectCount: { type: integer }
|
|
totalSize: { type: integer }
|
|
lastModified: { type: string, format: date-time, nullable: true }
|
|
truncated: { type: boolean }
|
|
publicRead: { type: boolean }
|
|
error: { type: string, nullable: true }
|
|
totals:
|
|
type: object
|
|
required: [buckets, objectCount, totalSize]
|
|
properties:
|
|
buckets: { type: integer }
|
|
objectCount: { type: integer }
|
|
totalSize: { type: integer }
|
|
cachedAt:
|
|
type: string
|
|
format: date-time
|
|
AuthError:
|
|
type: object
|
|
required: [message]
|
|
properties:
|
|
message:
|
|
type: string
|
|
example: Invalid password
|
|
S3Error:
|
|
type: object
|
|
xml: { name: Error }
|
|
required: [Code, Message, RequestId]
|
|
properties:
|
|
Code: { type: string, xml: { name: Code } }
|
|
Message: { type: string, xml: { name: Message } }
|
|
Resource: { type: string, xml: { name: Resource } }
|
|
RequestId: { type: string, xml: { name: RequestId } }
|
|
Object:
|
|
type: object
|
|
xml: { name: Contents }
|
|
properties:
|
|
Key: { type: string }
|
|
LastModified: { type: string, format: date-time }
|
|
ETag: { type: string }
|
|
Size: { type: integer }
|
|
StorageClass: { type: string, example: STANDARD }
|
|
ListBucketResult:
|
|
type: object
|
|
xml: { name: ListBucketResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
|
|
properties:
|
|
Name: { type: string }
|
|
Prefix: { type: string }
|
|
Delimiter: { type: string }
|
|
MaxKeys: { type: integer, example: 1000 }
|
|
IsTruncated: { type: boolean }
|
|
Contents: { type: array, items: { $ref: '#/components/schemas/Object' }, xml: { wrapped: false } }
|
|
InitiateMultipartUploadResult:
|
|
type: object
|
|
xml: { name: InitiateMultipartUploadResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
|
|
required: [Bucket, Key, UploadId]
|
|
properties:
|
|
Bucket: { type: string }
|
|
Key: { type: string }
|
|
UploadId: { type: string }
|
|
CompleteMultipartUpload:
|
|
type: object
|
|
xml: { name: CompleteMultipartUpload }
|
|
required: [Part]
|
|
properties:
|
|
Part:
|
|
type: array
|
|
minItems: 1
|
|
xml: { wrapped: false }
|
|
items:
|
|
type: object
|
|
xml: { name: Part }
|
|
required: [PartNumber, ETag]
|
|
properties:
|
|
PartNumber: { type: integer }
|
|
ETag: { type: string }
|
|
CompleteMultipartUploadResult:
|
|
type: object
|
|
xml: { name: CompleteMultipartUploadResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
|
|
properties:
|
|
Location: { type: string }
|
|
Bucket: { type: string }
|
|
Key: { type: string }
|
|
ETag: { type: string }
|
|
ListPartsResult:
|
|
type: object
|
|
xml: { name: ListPartsResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
|
|
properties:
|
|
Bucket: { type: string }
|
|
Key: { type: string }
|
|
UploadId: { type: string }
|
|
NextPartNumberMarker: { type: integer }
|
|
IsTruncated: { type: boolean }
|
|
Part:
|
|
type: array
|
|
xml: { wrapped: false }
|
|
items:
|
|
type: object
|
|
properties:
|
|
PartNumber: { type: integer }
|
|
LastModified: { type: string, format: date-time }
|
|
ETag: { type: string }
|
|
Size: { type: integer }
|
|
ListMultipartUploadsResult:
|
|
type: object
|
|
xml: { name: ListMultipartUploadsResult, namespace: 'http://s3.amazonaws.com/doc/2006-03-01/' }
|
|
properties:
|
|
Bucket: { type: string }
|
|
KeyMarker: { type: string }
|
|
UploadIdMarker: { type: string }
|
|
NextKeyMarker: { type: string }
|
|
NextUploadIdMarker: { type: string }
|
|
MaxUploads: { type: integer, example: 1000 }
|
|
IsTruncated: { type: boolean, example: false }
|