fix(registry): align registry integrations with updated auth, storage, repository, and audit models
This commit is contained in:
@@ -46,206 +46,114 @@ export class StackGalleryAuthProvider implements plugins.smartregistry.IAuthProv
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a request and return the actor
|
||||
* Called by smartregistry for every incoming request
|
||||
* Authenticate with username/password credentials
|
||||
* Returns userId on success, null on failure
|
||||
*/
|
||||
public async authenticate(request: plugins.smartregistry.IAuthRequest): Promise<plugins.smartregistry.IRequestActor> {
|
||||
const auditContext = AuditService.withContext({
|
||||
actorIp: request.ip,
|
||||
actorUserAgent: request.userAgent,
|
||||
});
|
||||
|
||||
// Extract auth credentials
|
||||
const authHeader = request.headers?.['authorization'] || request.headers?.['Authorization'];
|
||||
|
||||
// Try Bearer token (API token)
|
||||
if (authHeader?.startsWith('Bearer ')) {
|
||||
const token = authHeader.substring(7);
|
||||
return await this.authenticateWithApiToken(token, request, auditContext);
|
||||
}
|
||||
|
||||
// Try Basic auth (for npm/other CLI tools)
|
||||
if (authHeader?.startsWith('Basic ')) {
|
||||
const credentials = authHeader.substring(6);
|
||||
return await this.authenticateWithBasicAuth(credentials, request, auditContext);
|
||||
}
|
||||
|
||||
// Anonymous access
|
||||
return this.createAnonymousActor(request);
|
||||
public async authenticate(
|
||||
credentials: plugins.smartregistry.ICredentials
|
||||
): Promise<string | null> {
|
||||
const result = await this.authService.login(credentials.username, credentials.password);
|
||||
if (!result.success || !result.user) return null;
|
||||
return result.user.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if actor has permission for the requested action
|
||||
* Validate a token and return auth token info
|
||||
*/
|
||||
public async validateToken(
|
||||
token: string,
|
||||
protocol?: plugins.smartregistry.TRegistryProtocol
|
||||
): Promise<plugins.smartregistry.IAuthToken | null> {
|
||||
// Try API token (srg_ prefix)
|
||||
if (token.startsWith('srg_')) {
|
||||
const result = await this.tokenService.validateToken(token);
|
||||
if (!result.valid || !result.token || !result.user) return null;
|
||||
|
||||
return {
|
||||
type: (protocol || result.token.protocols[0] || 'npm') as plugins.smartregistry.TRegistryProtocol,
|
||||
userId: result.user.id,
|
||||
scopes: result.token.scopes.map((s) =>
|
||||
`${s.protocol}:${s.actions.join(',')}`
|
||||
),
|
||||
readonly: !result.token.scopes.some((s) =>
|
||||
s.actions.includes('write') || s.actions.includes('*')
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
// Try JWT access token
|
||||
const validated = await this.authService.validateAccessToken(token);
|
||||
if (!validated) return null;
|
||||
|
||||
return {
|
||||
type: (protocol || 'npm') as plugins.smartregistry.TRegistryProtocol,
|
||||
userId: validated.user.id,
|
||||
scopes: ['*'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new token for a user and protocol
|
||||
*/
|
||||
public async createToken(
|
||||
userId: string,
|
||||
protocol: plugins.smartregistry.TRegistryProtocol,
|
||||
options?: plugins.smartregistry.ITokenOptions
|
||||
): Promise<string> {
|
||||
const result = await this.tokenService.createToken({
|
||||
userId,
|
||||
name: `${protocol}-token`,
|
||||
protocols: [protocol as TRegistryProtocol],
|
||||
scopes: [
|
||||
{
|
||||
protocol: protocol as TRegistryProtocol,
|
||||
actions: options?.readonly ? ['read'] : ['read', 'write', 'delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
return result.rawToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a token
|
||||
*/
|
||||
public async revokeToken(token: string): Promise<void> {
|
||||
if (token.startsWith('srg_')) {
|
||||
// Hash and find the token
|
||||
const result = await this.tokenService.validateToken(token);
|
||||
if (result.valid && result.token) {
|
||||
await this.tokenService.revokeToken(result.token.id, 'provider_revoked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a token holder is authorized for a resource and action
|
||||
*/
|
||||
public async authorize(
|
||||
actor: plugins.smartregistry.IRequestActor,
|
||||
request: plugins.smartregistry.IAuthorizationRequest
|
||||
): Promise<plugins.smartregistry.IAuthorizationResult> {
|
||||
const stackActor = actor as IStackGalleryActor;
|
||||
token: plugins.smartregistry.IAuthToken | null,
|
||||
resource: string,
|
||||
action: string
|
||||
): Promise<boolean> {
|
||||
// Anonymous access: only public reads
|
||||
if (!token) return false;
|
||||
|
||||
// Anonymous users can only read public packages
|
||||
if (stackActor.type === 'anonymous') {
|
||||
if (request.action === 'read' && request.isPublic) {
|
||||
return { allowed: true };
|
||||
}
|
||||
return {
|
||||
allowed: false,
|
||||
reason: 'Authentication required',
|
||||
statusCode: 401,
|
||||
};
|
||||
}
|
||||
// Parse resource string (format: "protocol:type:name" or "org/repo")
|
||||
const userId = token.userId;
|
||||
if (!userId) return false;
|
||||
|
||||
// Check protocol access
|
||||
if (!stackActor.protocols.includes(request.protocol as TRegistryProtocol) &&
|
||||
!stackActor.protocols.includes('*' as TRegistryProtocol)) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Token does not have access to ${request.protocol} protocol`,
|
||||
statusCode: 403,
|
||||
};
|
||||
}
|
||||
// Map action
|
||||
const mappedAction = this.mapAction(action);
|
||||
|
||||
// Map action to TAction
|
||||
const action = this.mapAction(request.action);
|
||||
// For simple authorization without specific resource context,
|
||||
// check if user is active
|
||||
const user = await User.findById(userId);
|
||||
if (!user || !user.isActive) return false;
|
||||
|
||||
// Resolve permissions
|
||||
const permissions = await this.permissionService.resolvePermissions({
|
||||
userId: stackActor.userId!,
|
||||
organizationId: request.organizationId,
|
||||
repositoryId: request.repositoryId,
|
||||
protocol: request.protocol as TRegistryProtocol,
|
||||
});
|
||||
// System admins bypass all checks
|
||||
if (user.isSystemAdmin) return true;
|
||||
|
||||
// Check permission
|
||||
let allowed = false;
|
||||
switch (action) {
|
||||
case 'read':
|
||||
allowed = permissions.canRead || (request.isPublic ?? false);
|
||||
break;
|
||||
case 'write':
|
||||
allowed = permissions.canWrite;
|
||||
break;
|
||||
case 'delete':
|
||||
allowed = permissions.canDelete;
|
||||
break;
|
||||
case 'admin':
|
||||
allowed = permissions.canAdmin;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Insufficient permissions for ${request.action} on ${request.resourceType}`,
|
||||
statusCode: 403,
|
||||
};
|
||||
}
|
||||
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using API token
|
||||
*/
|
||||
private async authenticateWithApiToken(
|
||||
rawToken: string,
|
||||
request: plugins.smartregistry.IAuthRequest,
|
||||
auditContext: AuditService
|
||||
): Promise<IStackGalleryActor> {
|
||||
const result = await this.tokenService.validateToken(rawToken, request.ip);
|
||||
|
||||
if (!result.valid || !result.token || !result.user) {
|
||||
await auditContext.logFailure(
|
||||
'TOKEN_USED',
|
||||
'api_token',
|
||||
result.errorCode || 'UNKNOWN',
|
||||
result.errorMessage || 'Token validation failed'
|
||||
);
|
||||
|
||||
return this.createAnonymousActor(request);
|
||||
}
|
||||
|
||||
await auditContext.log('TOKEN_USED', 'api_token', {
|
||||
resourceId: result.token.id,
|
||||
success: true,
|
||||
});
|
||||
|
||||
return {
|
||||
type: 'api_token',
|
||||
userId: result.user.id,
|
||||
user: result.user,
|
||||
tokenId: result.token.id,
|
||||
ip: request.ip,
|
||||
userAgent: request.userAgent,
|
||||
protocols: result.token.protocols,
|
||||
permissions: {
|
||||
canRead: true,
|
||||
canWrite: true,
|
||||
canDelete: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate using Basic auth (username:password or username:token)
|
||||
*/
|
||||
private async authenticateWithBasicAuth(
|
||||
credentials: string,
|
||||
request: plugins.smartregistry.IAuthRequest,
|
||||
auditContext: AuditService
|
||||
): Promise<IStackGalleryActor> {
|
||||
try {
|
||||
const decoded = atob(credentials);
|
||||
const [username, password] = decoded.split(':');
|
||||
|
||||
// If password looks like an API token, try token auth
|
||||
if (password?.startsWith('srg_')) {
|
||||
return await this.authenticateWithApiToken(password, request, auditContext);
|
||||
}
|
||||
|
||||
// Otherwise try username/password (email/password)
|
||||
const result = await this.authService.login(username, password, {
|
||||
userAgent: request.userAgent,
|
||||
ipAddress: request.ip,
|
||||
});
|
||||
|
||||
if (!result.success || !result.user) {
|
||||
return this.createAnonymousActor(request);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'user',
|
||||
userId: result.user.id,
|
||||
user: result.user,
|
||||
ip: request.ip,
|
||||
userAgent: request.userAgent,
|
||||
protocols: ['npm', 'oci', 'maven', 'cargo', 'composer', 'pypi', 'rubygems'],
|
||||
permissions: {
|
||||
canRead: true,
|
||||
canWrite: true,
|
||||
canDelete: true,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return this.createAnonymousActor(request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create anonymous actor
|
||||
*/
|
||||
private createAnonymousActor(request: plugins.smartregistry.IAuthRequest): IStackGalleryActor {
|
||||
return {
|
||||
type: 'anonymous',
|
||||
ip: request.ip,
|
||||
userAgent: request.userAgent,
|
||||
protocols: [],
|
||||
permissions: {
|
||||
canRead: false,
|
||||
canWrite: false,
|
||||
canDelete: false,
|
||||
},
|
||||
};
|
||||
return mappedAction === 'read'; // Default: authenticated users can read
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
import * as plugins from '../plugins.ts';
|
||||
import type { TRegistryProtocol } from '../interfaces/auth.interfaces.ts';
|
||||
import { Package } from '../models/package.ts';
|
||||
import { Repository } from '../models/repository.ts';
|
||||
import { Organization } from '../models/organization.ts';
|
||||
import { AuditService } from '../services/audit.service.ts';
|
||||
|
||||
export interface IStorageConfig {
|
||||
export interface IStorageProviderConfig {
|
||||
bucket: plugins.smartbucket.SmartBucket;
|
||||
bucketName: string;
|
||||
basePath: string;
|
||||
}
|
||||
|
||||
@@ -20,222 +20,192 @@ export interface IStorageConfig {
|
||||
* and stores artifacts in S3 via smartbucket
|
||||
*/
|
||||
export class StackGalleryStorageHooks implements plugins.smartregistry.IStorageHooks {
|
||||
private config: IStorageConfig;
|
||||
private config: IStorageProviderConfig;
|
||||
|
||||
constructor(config: IStorageConfig) {
|
||||
constructor(config: IStorageProviderConfig) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a package is stored
|
||||
* Use this to validate, transform, or prepare for storage
|
||||
*/
|
||||
public async beforeStore(context: plugins.smartregistry.IStorageContext): Promise<plugins.smartregistry.IStorageContext> {
|
||||
public async beforePut(
|
||||
context: plugins.smartregistry.IStorageHookContext
|
||||
): Promise<plugins.smartregistry.IBeforePutResult> {
|
||||
// Validate organization exists and has quota
|
||||
const org = await Organization.findById(context.organizationId);
|
||||
if (!org) {
|
||||
throw new Error(`Organization not found: ${context.organizationId}`);
|
||||
}
|
||||
const orgId = context.actor?.orgId;
|
||||
if (orgId) {
|
||||
const org = await Organization.findById(orgId);
|
||||
if (!org) {
|
||||
return { allowed: false, reason: `Organization not found: ${orgId}` };
|
||||
}
|
||||
|
||||
// Check storage quota
|
||||
const newSize = context.size || 0;
|
||||
if (org.settings.quotas.maxStorageBytes > 0) {
|
||||
if (org.usedStorageBytes + newSize > org.settings.quotas.maxStorageBytes) {
|
||||
throw new Error('Organization storage quota exceeded');
|
||||
// Check storage quota
|
||||
const newSize = context.metadata?.size || 0;
|
||||
if (!org.hasStorageAvailable(newSize)) {
|
||||
return { allowed: false, reason: 'Organization storage quota exceeded' };
|
||||
}
|
||||
}
|
||||
|
||||
// Validate repository exists
|
||||
const repo = await Repository.findById(context.repositoryId);
|
||||
if (!repo) {
|
||||
throw new Error(`Repository not found: ${context.repositoryId}`);
|
||||
}
|
||||
|
||||
// Check repository protocol
|
||||
if (!repo.protocols.includes(context.protocol as TRegistryProtocol)) {
|
||||
throw new Error(`Repository does not support ${context.protocol} protocol`);
|
||||
}
|
||||
|
||||
return context;
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a package is successfully stored
|
||||
* Update database records and metrics
|
||||
*/
|
||||
public async afterStore(context: plugins.smartregistry.IStorageContext): Promise<void> {
|
||||
public async afterPut(
|
||||
context: plugins.smartregistry.IStorageHookContext
|
||||
): Promise<void> {
|
||||
const protocol = context.protocol as TRegistryProtocol;
|
||||
const packageId = Package.generateId(protocol, context.organizationName, context.packageName);
|
||||
const packageName = context.metadata?.packageName || context.key;
|
||||
const version = context.metadata?.version || 'unknown';
|
||||
const orgId = context.actor?.orgId || '';
|
||||
|
||||
const packageId = Package.generateId(protocol, orgId, packageName);
|
||||
|
||||
// Get or create package record
|
||||
let pkg = await Package.findById(packageId);
|
||||
if (!pkg) {
|
||||
pkg = new Package();
|
||||
pkg.id = packageId;
|
||||
pkg.organizationId = context.organizationId;
|
||||
pkg.repositoryId = context.repositoryId;
|
||||
pkg.organizationId = orgId;
|
||||
pkg.protocol = protocol;
|
||||
pkg.name = context.packageName;
|
||||
pkg.createdById = context.actorId || '';
|
||||
pkg.name = packageName;
|
||||
pkg.createdById = context.actor?.userId || '';
|
||||
pkg.createdAt = new Date();
|
||||
}
|
||||
|
||||
// Add version
|
||||
pkg.addVersion({
|
||||
version: context.version,
|
||||
version,
|
||||
publishedAt: new Date(),
|
||||
publishedBy: context.actorId || '',
|
||||
size: context.size || 0,
|
||||
checksum: context.checksum || '',
|
||||
checksumAlgorithm: context.checksumAlgorithm || 'sha256',
|
||||
publishedById: context.actor?.userId || '',
|
||||
size: context.metadata?.size || 0,
|
||||
digest: context.metadata?.digest,
|
||||
downloads: 0,
|
||||
metadata: context.metadata || {},
|
||||
metadata: {},
|
||||
});
|
||||
|
||||
// Update dist tags if provided
|
||||
if (context.tags) {
|
||||
for (const [tag, version] of Object.entries(context.tags)) {
|
||||
pkg.distTags[tag] = version;
|
||||
}
|
||||
}
|
||||
|
||||
// Set latest tag if not set
|
||||
if (!pkg.distTags['latest']) {
|
||||
pkg.distTags['latest'] = context.version;
|
||||
pkg.distTags['latest'] = version;
|
||||
}
|
||||
|
||||
await pkg.save();
|
||||
|
||||
// Update organization storage usage
|
||||
const org = await Organization.findById(context.organizationId);
|
||||
if (org) {
|
||||
org.usedStorageBytes += context.size || 0;
|
||||
await org.save();
|
||||
if (orgId) {
|
||||
const org = await Organization.findById(orgId);
|
||||
if (org) {
|
||||
await org.updateStorageUsage(context.metadata?.size || 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: context.actorId,
|
||||
actorType: context.actorId ? 'user' : 'anonymous',
|
||||
organizationId: context.organizationId,
|
||||
repositoryId: context.repositoryId,
|
||||
}).logPackagePublished(
|
||||
packageId,
|
||||
context.packageName,
|
||||
context.version,
|
||||
context.organizationId,
|
||||
context.repositoryId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a package is fetched
|
||||
*/
|
||||
public async beforeFetch(context: plugins.smartregistry.IFetchContext): Promise<plugins.smartregistry.IFetchContext> {
|
||||
return context;
|
||||
if (context.actor?.userId) {
|
||||
await AuditService.withContext({
|
||||
actorId: context.actor.userId,
|
||||
actorType: 'user',
|
||||
organizationId: orgId,
|
||||
}).logPackagePublished(packageId, packageName, version, orgId, '');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a package is fetched
|
||||
* Update download metrics
|
||||
*/
|
||||
public async afterFetch(context: plugins.smartregistry.IFetchContext): Promise<void> {
|
||||
public async afterGet(
|
||||
context: plugins.smartregistry.IStorageHookContext
|
||||
): Promise<void> {
|
||||
const protocol = context.protocol as TRegistryProtocol;
|
||||
const packageId = Package.generateId(protocol, context.organizationName, context.packageName);
|
||||
const packageName = context.metadata?.packageName || context.key;
|
||||
const version = context.metadata?.version;
|
||||
const orgId = context.actor?.orgId || '';
|
||||
|
||||
const packageId = Package.generateId(protocol, orgId, packageName);
|
||||
|
||||
const pkg = await Package.findById(packageId);
|
||||
if (pkg) {
|
||||
await pkg.incrementDownloads(context.version);
|
||||
}
|
||||
|
||||
// Audit log for authenticated users
|
||||
if (context.actorId) {
|
||||
await AuditService.withContext({
|
||||
actorId: context.actorId,
|
||||
actorType: 'user',
|
||||
organizationId: context.organizationId,
|
||||
repositoryId: context.repositoryId,
|
||||
}).logPackageDownloaded(
|
||||
packageId,
|
||||
context.packageName,
|
||||
context.version || 'latest',
|
||||
context.organizationId,
|
||||
context.repositoryId
|
||||
);
|
||||
await pkg.incrementDownloads(version);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before a package is deleted
|
||||
*/
|
||||
public async beforeDelete(context: plugins.smartregistry.IDeleteContext): Promise<plugins.smartregistry.IDeleteContext> {
|
||||
return context;
|
||||
public async beforeDelete(
|
||||
context: plugins.smartregistry.IStorageHookContext
|
||||
): Promise<plugins.smartregistry.IBeforeDeleteResult> {
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a package is deleted
|
||||
*/
|
||||
public async afterDelete(context: plugins.smartregistry.IDeleteContext): Promise<void> {
|
||||
public async afterDelete(
|
||||
context: plugins.smartregistry.IStorageHookContext
|
||||
): Promise<void> {
|
||||
const protocol = context.protocol as TRegistryProtocol;
|
||||
const packageId = Package.generateId(protocol, context.organizationName, context.packageName);
|
||||
const packageName = context.metadata?.packageName || context.key;
|
||||
const version = context.metadata?.version;
|
||||
const orgId = context.actor?.orgId || '';
|
||||
|
||||
const packageId = Package.generateId(protocol, orgId, packageName);
|
||||
|
||||
const pkg = await Package.findById(packageId);
|
||||
if (!pkg) return;
|
||||
|
||||
if (context.version) {
|
||||
// Delete specific version
|
||||
const version = pkg.versions[context.version];
|
||||
if (version) {
|
||||
const sizeReduction = version.size;
|
||||
delete pkg.versions[context.version];
|
||||
if (version) {
|
||||
const versionData = pkg.versions[version];
|
||||
if (versionData) {
|
||||
const sizeReduction = versionData.size;
|
||||
delete pkg.versions[version];
|
||||
pkg.storageBytes -= sizeReduction;
|
||||
|
||||
// Update dist tags
|
||||
for (const [tag, ver] of Object.entries(pkg.distTags)) {
|
||||
if (ver === context.version) {
|
||||
if (ver === version) {
|
||||
delete pkg.distTags[tag];
|
||||
}
|
||||
}
|
||||
|
||||
// If no versions left, delete the package
|
||||
if (Object.keys(pkg.versions).length === 0) {
|
||||
await pkg.delete();
|
||||
} else {
|
||||
await pkg.save();
|
||||
}
|
||||
|
||||
// Update org storage
|
||||
const org = await Organization.findById(context.organizationId);
|
||||
if (org) {
|
||||
org.usedStorageBytes -= sizeReduction;
|
||||
await org.save();
|
||||
if (orgId) {
|
||||
const org = await Organization.findById(orgId);
|
||||
if (org) {
|
||||
await org.updateStorageUsage(-sizeReduction);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Delete entire package
|
||||
const sizeReduction = pkg.storageBytes;
|
||||
await pkg.delete();
|
||||
|
||||
// Update org storage
|
||||
const org = await Organization.findById(context.organizationId);
|
||||
if (org) {
|
||||
org.usedStorageBytes -= sizeReduction;
|
||||
await org.save();
|
||||
if (orgId) {
|
||||
const org = await Organization.findById(orgId);
|
||||
if (org) {
|
||||
await org.updateStorageUsage(-sizeReduction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await AuditService.withContext({
|
||||
actorId: context.actorId,
|
||||
actorType: context.actorId ? 'user' : 'system',
|
||||
organizationId: context.organizationId,
|
||||
repositoryId: context.repositoryId,
|
||||
}).log('PACKAGE_DELETED', 'package', {
|
||||
resourceId: packageId,
|
||||
resourceName: context.packageName,
|
||||
metadata: { version: context.version },
|
||||
success: true,
|
||||
});
|
||||
if (context.actor?.userId) {
|
||||
await AuditService.withContext({
|
||||
actorId: context.actor.userId,
|
||||
actorType: 'user',
|
||||
organizationId: orgId,
|
||||
}).log('PACKAGE_DELETED', 'package', {
|
||||
resourceId: packageId,
|
||||
resourceName: packageName,
|
||||
metadata: { version },
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,11 +229,10 @@ export class StackGalleryStorageHooks implements plugins.smartregistry.IStorageH
|
||||
data: Uint8Array,
|
||||
contentType?: string
|
||||
): Promise<string> {
|
||||
const bucket = await this.config.bucket.getBucket();
|
||||
const bucket = await this.config.bucket.getBucketByName(this.config.bucketName);
|
||||
await bucket.fastPut({
|
||||
path,
|
||||
contents: Buffer.from(data),
|
||||
contentType: contentType || 'application/octet-stream',
|
||||
contents: data as unknown as string,
|
||||
});
|
||||
return path;
|
||||
}
|
||||
@@ -273,10 +242,10 @@ export class StackGalleryStorageHooks implements plugins.smartregistry.IStorageH
|
||||
*/
|
||||
public async fetchArtifact(path: string): Promise<Uint8Array | null> {
|
||||
try {
|
||||
const bucket = await this.config.bucket.getBucket();
|
||||
const bucket = await this.config.bucket.getBucketByName(this.config.bucketName);
|
||||
const file = await bucket.fastGet({ path });
|
||||
if (!file) return null;
|
||||
return new Uint8Array(file.contents);
|
||||
return new Uint8Array(file);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -287,8 +256,8 @@ export class StackGalleryStorageHooks implements plugins.smartregistry.IStorageH
|
||||
*/
|
||||
public async deleteArtifact(path: string): Promise<boolean> {
|
||||
try {
|
||||
const bucket = await this.config.bucket.getBucket();
|
||||
await bucket.fastDelete({ path });
|
||||
const bucket = await this.config.bucket.getBucketByName(this.config.bucketName);
|
||||
await bucket.fastRemove({ path });
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user