Skip to content
3 changes: 3 additions & 0 deletions src/@types/fileObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ export interface UrlFileObject extends BaseFileObject {
url: string
method: string
headers?: [HeadersObject]
fileHash?: string
}

export interface IpfsFileObject extends BaseFileObject {
hash: string
fileHash?: string
}
export interface S3Object {
endpoint: string
Expand All @@ -39,6 +41,7 @@ export interface S3FileObject extends BaseFileObject {

export interface ArweaveFileObject extends BaseFileObject {
transactionId: string
fileHash?: string
}

export interface StorageReadable {
Expand Down
75 changes: 57 additions & 18 deletions src/components/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export abstract class Storage {
}

abstract validate(): [boolean, string]
abstract getDownloadUrl(): string
abstract getDownloadUrl(): Promise<string>

abstract fetchSpecificFileMetadata(
fileObject: any,
Expand All @@ -48,7 +48,7 @@ export abstract class Storage {

// similar to all subclasses
async getReadableStream(): Promise<StorageReadable> {
const input = this.getDownloadUrl()
const input = await this.getDownloadUrl()
const response = await axios({
method: 'get',
url: input,
Expand Down Expand Up @@ -183,6 +183,14 @@ export abstract class Storage {
return false
}
}

async decryptUrl(hashedUrl: string, encryptedMethod: EncryptMethod): Promise<string> {
const decodedUrlBuffer = Buffer.from(hashedUrl, 'base64')
const decryptedBuffer = await decryptData(decodedUrlBuffer, encryptedMethod)
const decoder = new TextDecoder()
const decryptedUrl = decoder.decode(decryptedBuffer)
return decryptedUrl
}
}

export class UrlStorage extends Storage {
Expand Down Expand Up @@ -222,16 +230,26 @@ export class UrlStorage extends Storage {

isFilePath(): boolean {
const regex: RegExp = /^(.+)\/([^/]*)$/ // The URL should not represent a path
const { url } = this.getFile()
if (url.startsWith('http://') || url.startsWith('https://')) {
const file = this.getFile()
if (
file.url.startsWith('http://') ||
file.url.startsWith('https://') ||
(file.encryptedBy && file.encryptedMethod)
) {
return false
}
return regex.test(url)
return regex.test(file.url)
}

getDownloadUrl(): string {
async getDownloadUrl(): Promise<string> {
if (this.validate()[0] === true) {
return this.getFile().url
const file = this.getFile()
if (file?.encryptedBy && file?.encryptedMethod) {
const decryptedUrl = await this.decryptUrl(file.url, file.encryptedMethod)
return decryptedUrl
} else {
return file.url
}
}
return null
}
Expand All @@ -240,12 +258,16 @@ export class UrlStorage extends Storage {
fileObject: UrlFileObject,
forceChecksum: boolean
): Promise<FileInfoResponse> {
const { url, method } = fileObject
const url = await this.getDownloadUrl()
const { method, fileHash } = fileObject
const { contentLength, contentType, contentChecksum } = await fetchFileMetadata(
url,
method,
forceChecksum
)
if (forceChecksum && fileHash && contentChecksum !== fileHash) {
throw new Error(`Error checksum`)
}
return {
valid: true,
contentLength,
Expand Down Expand Up @@ -311,20 +333,26 @@ export class ArweaveStorage extends Storage {
return regex.test(transactionId)
}

getDownloadUrl(): string {
return urlJoin(process.env.ARWEAVE_GATEWAY, this.getFile().transactionId)
getDownloadUrl(): Promise<string> {
return Promise.resolve(
urlJoin(process.env.ARWEAVE_GATEWAY, this.getFile().transactionId)
)
}

async fetchSpecificFileMetadata(
fileObject: ArweaveFileObject,
forceChecksum: boolean
): Promise<FileInfoResponse> {
const { fileHash } = fileObject
const url = urlJoin(process.env.ARWEAVE_GATEWAY, fileObject.transactionId)
const { contentLength, contentType, contentChecksum } = await fetchFileMetadata(
url,
'get',
forceChecksum
)
if (forceChecksum && fileHash && contentChecksum !== fileHash) {
throw new Error(`Error checksum`)
}
return {
valid: true,
contentLength,
Expand Down Expand Up @@ -378,25 +406,36 @@ export class IpfsStorage extends Storage {

isFilePath(): boolean {
const regex: RegExp = /^(.+)\/([^/]*)$/ // The CID should not represent a path
const { hash } = this.getFile()

return regex.test(hash)
const file = this.getFile()
if (file.encryptedBy && file.encryptedMethod) {
return false
}
return regex.test(file.hash)
}

getDownloadUrl(): string {
return urlJoin(process.env.IPFS_GATEWAY, urlJoin('/ipfs', this.getFile().hash))
async getDownloadUrl(): Promise<string> {
const file = this.getFile()
if (file?.encryptedBy && file?.encryptedMethod) {
const decryptedUrl = await this.decryptUrl(file.hash, file.encryptedMethod)
return urlJoin(process.env.IPFS_GATEWAY, urlJoin('/ipfs', decryptedUrl))
}
return urlJoin(process.env.IPFS_GATEWAY, urlJoin('/ipfs', file.hash))
}

async fetchSpecificFileMetadata(
fileObject: IpfsFileObject,
forceChecksum: boolean
): Promise<FileInfoResponse> {
const url = urlJoin(process.env.IPFS_GATEWAY, urlJoin('/ipfs', fileObject.hash))
const url = await this.getDownloadUrl()
const { fileHash } = fileObject
const { contentLength, contentType, contentChecksum } = await fetchFileMetadata(
url,
'get',
forceChecksum
)
if (forceChecksum && fileHash && contentChecksum !== fileHash) {
throw new Error(`Error checksum`)
}
return {
valid: true,
contentLength,
Expand Down Expand Up @@ -463,9 +502,9 @@ export class S3Storage extends Storage {
return endpoint.includes('.')
}

getDownloadUrl(): string {
getDownloadUrl(): Promise<string> {
const { s3Access } = this.getFile()
return JSON.stringify(s3Access)
return Promise.resolve(JSON.stringify(s3Access))
}

async fetchDataContent(): Promise<any> {
Expand Down
12 changes: 11 additions & 1 deletion src/test/data/ddo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,16 @@ export const remoteDDOTypeURLNotEncrypted = {
}
}

export const remoteDDOTypeURLEncrypted = {
remote: {
type: 'url',
url: '',
method: 'GET',
encryptedBy: '16Uiu2HAmN211yBiE6dF5xu8GFXV1jqZQzK5MbzBuQDspfa6qNgXF',
encryptedMethod: 'ECIES'
}
}

export const remoteDDOTypeIPFSNotEncrypted = {
remote: {
type: 'ipfs',
Expand All @@ -499,7 +509,7 @@ export const remoteDDOTypeIPFSNotEncrypted = {
export const remoteDDOTypeIPFSEncrypted = {
remote: {
type: 'ipfs',
hash: 'QmaD5S7TakPs3a4fijatbfqhmhhrEbCvbqGTTAp7VrZ91T',
hash: 'BK/WRmZCK4dN58E9E5ilUsmSP7q11P4ri9Y0A4WL4ealbr4crSrACw4Q7xbiYymjYw/noHErKVuOytGx9tzR8ThilK0cFodlQctQKaFewtBeYj4hhErIJkn+4MAV+dGsEnlZKT0IrmLI12MhnfRBLJ606AI0HGnOndGAiYJMhieNSfWMbvk8pYCIQ9P95OE=',
encryptedBy: '16Uiu2HAmN211yBiE6dF5xu8GFXV1jqZQzK5MbzBuQDspfa6qNgXF',
encryptedMethod: 'ECIES'
}
Expand Down
Binary file modified src/test/data/organizations-100.aes
Binary file not shown.
4 changes: 2 additions & 2 deletions src/test/unit/s3.storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ describe('S3 Storage tests', () => {
expect(parsedData).to.deep.equal({ key: 'value' })
})

it('should fetch data from s3', () => {
const result = s3Storage.getDownloadUrl()
it('should fetch data from s3', async () => {
const result = await s3Storage.getDownloadUrl()
expect(result).to.be.equal(JSON.stringify(s3Object))
})

Expand Down
Loading
Loading