scrt-link-core
Advanced tools
Comparing version 0.2.2 to 0.2.3
module.exports = { | ||
root: true, | ||
parser: '@typescript-eslint/parser', | ||
plugins: [ | ||
'@typescript-eslint', | ||
], | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:@typescript-eslint/recommended', | ||
], | ||
}; | ||
plugins: ['@typescript-eslint'], | ||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], | ||
} |
@@ -8,2 +8,9 @@ # Change Log | ||
## [0.2.3] - 2022-02-02 | ||
### Changed | ||
- Support file secret type | ||
- Add prettier, format code base | ||
## [0.2.2] - 2021-11-25 | ||
@@ -10,0 +17,0 @@ |
@@ -5,3 +5,3 @@ /** | ||
*/ | ||
import { CreateSecret, RetrieveSecret } from "./main"; | ||
import { CreateSecret, RetrieveSecret } from './main'; | ||
declare global { | ||
@@ -8,0 +8,0 @@ interface Window { |
@@ -6,3 +6,3 @@ "use strict"; | ||
exports.encryptionKeyLength = 24; // 14 | ||
exports.baseUrl = "https://scrt.link"; | ||
exports.baseUrl = 'https://scrt.link'; | ||
//# sourceMappingURL=constants.js.map |
@@ -1,3 +0,3 @@ | ||
import { encryptMessage, decryptMessage, generateAlias, generateEncryptionKey } from "./utils"; | ||
import { CreateSecretOptions, SecretUrlFields } from "./types"; | ||
import { encryptMessage, decryptMessage, generateAlias, generateEncryptionKey } from './utils'; | ||
import { CreateSecretOptions, SecretUrlFields } from './types'; | ||
export declare type CreateSecret = (message: string, options?: CreateSecretOptions, baseUrl?: string) => Promise<{ | ||
@@ -4,0 +4,0 @@ alias: string; |
@@ -13,3 +13,3 @@ "use strict"; | ||
const createSecret = (message, options = {}, baseUrl = constants_1.baseUrl) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { | ||
const { alias = utils_1.generateAlias(), encryptionKey = utils_1.generateEncryptionKey(), password, secretType = "text", neogramDestructionMessage = "This message will self-destruct in…", neogramDestructionTimeout = 3, receiptApi, receiptEmail, receiptPhoneNumber, } = options; | ||
const { alias = utils_1.generateAlias(), encryptionKey = utils_1.generateEncryptionKey(), password, secretType = 'text', neogramDestructionMessage = 'This message will self-destruct in…', neogramDestructionTimeout = 3, receiptApi, receiptEmail, receiptPhoneNumber, file, } = options; | ||
if (password && ramda_1.is(String, password)) { | ||
@@ -20,5 +20,5 @@ message = utils_1.encryptMessage(message, password); | ||
message = utils_1.encryptMessage(message, encryptionKey); | ||
const data = Object.assign(Object.assign({ alias, | ||
const data = Object.assign(Object.assign(Object.assign({ alias, | ||
message, | ||
secretType, isEncryptedWithUserPassword: !!password }, (secretType === "neogram" | ||
secretType, isEncryptedWithUserPassword: !!password }, (secretType === 'neogram' | ||
? { | ||
@@ -30,4 +30,8 @@ neogramDestructionMessage, | ||
receiptEmail, | ||
receiptPhoneNumber }); | ||
return utils_1.api(`${baseUrl}/api/secrets`, { method: "POST" }, ramda_1.reject(ramda_1.isNil, data)).then(() => ({ | ||
receiptPhoneNumber }), (secretType === 'file' | ||
? { | ||
file, | ||
} | ||
: {})); | ||
return utils_1.api(`${baseUrl}/api/secrets`, { method: 'POST' }, ramda_1.reject(ramda_1.isNil, data)).then(() => ({ | ||
alias, | ||
@@ -41,3 +45,3 @@ encryptionKey, | ||
const secretRaw = yield utils_1.api(`${baseUrl}/api/secrets/${alias}`, { | ||
method: "DELETE", | ||
method: 'DELETE', | ||
}); | ||
@@ -49,3 +53,3 @@ if (!secretRaw.message) { | ||
if (!result) { | ||
throw new Error("Decryption failed."); | ||
throw new Error('Decryption failed.'); | ||
} | ||
@@ -52,0 +56,0 @@ return Object.assign(Object.assign({}, secretRaw), { message: result }); |
@@ -1,2 +0,2 @@ | ||
export declare type SecretType = "text" | "url" | "neogram"; | ||
export declare type SecretType = 'text' | 'url' | 'neogram' | 'file'; | ||
export interface CreateSecretOptions { | ||
@@ -12,6 +12,13 @@ alias?: string; | ||
receiptApi?: Record<string, unknown>; | ||
file?: { | ||
bucket: string; | ||
key: string; | ||
type: string; | ||
name: string; | ||
size: number; | ||
}; | ||
} | ||
export declare type SecretUrlFields = Omit<CreateSecretOptions, "encryptionKey" | "password"> & { | ||
export declare type SecretUrlFields = Omit<CreateSecretOptions, 'encryptionKey' | 'password'> & { | ||
message: string; | ||
isEncryptedWithUserPassword: boolean; | ||
}; |
@@ -13,3 +13,3 @@ "use strict"; | ||
// Default options are marked with * | ||
const response = yield cross_fetch_1.default(url, Object.assign(Object.assign({ method: "GET", headers: Object.assign({ "Content-Type": "application/json" }, options === null || options === void 0 ? void 0 : options.headers) }, options), (data ? { body: JSON.stringify(data) } : {}))); | ||
const response = yield cross_fetch_1.default(url, Object.assign(Object.assign({ method: 'GET', headers: Object.assign({ 'Content-Type': 'application/json' }, options === null || options === void 0 ? void 0 : options.headers) }, options), (data ? { body: JSON.stringify(data) } : {}))); | ||
if (!response.ok) { | ||
@@ -16,0 +16,0 @@ const errorResponse = yield response.json(); |
module.exports = { | ||
roots: ['<rootDir>/src'], | ||
testMatch: [ | ||
"**/__tests__/**/*.+(ts|tsx|js)", | ||
"**/?(*.)+(spec|test).+(ts|tsx|js)" | ||
], | ||
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], | ||
transform: { | ||
"^.+\\.(ts|tsx)$": "ts-jest" | ||
'^.+\\.(ts|tsx)$': 'ts-jest', | ||
}, | ||
} |
{ | ||
"name": "scrt-link-core", | ||
"version": "0.2.2", | ||
"version": "0.2.3", | ||
"description": "Core for scrt.link - a tool to securely share sensitive information online.", | ||
@@ -42,5 +42,6 @@ "author": "Chris Chiller <kingchiller@protonmail.com>", | ||
"@typescript-eslint/parser": "^4.19.0", | ||
"esbuild": "^0.11.11", | ||
"esbuild": "^0.13.15", | ||
"eslint": "^7.22.0", | ||
"jest": "^26.6.3", | ||
"prettier": "^2.5.1", | ||
"ts-jest": "^26.5.4", | ||
@@ -47,0 +48,0 @@ "ts-node": "^9.1.1", |
@@ -20,5 +20,5 @@ # scrt-link-core | ||
```ts | ||
import { createSecret } from "scrt-link-core"; | ||
import { createSecret } from 'scrt-link-core' | ||
const { secretLink } = await createSecret("Some confidential information…"); | ||
const { secretLink } = await createSecret('Some confidential information…') | ||
// https://scrt.link/l/CWmbcLtxzFRad8JJ#ReCMTkJkAtUqFF9ydBAWdYaz | ||
@@ -68,6 +68,6 @@ ``` | ||
<script type="module"> | ||
import { createSecret } from "https://cdn.skypack.dev/scrt-link-core"; | ||
import { createSecret } from 'https://cdn.skypack.dev/scrt-link-core' | ||
// Use as described above… | ||
createSecret("Some confidential information…").then(({ secretLink }) => {}); | ||
createSecret('Some confidential information…').then(({ secretLink }) => {}) | ||
</script> | ||
@@ -90,7 +90,5 @@ ``` | ||
// Functions are available on the window object | ||
window | ||
.createSecret("Some confidential information…") | ||
.then(({ secretLink }) => { | ||
console.log(`Success! Your secret link is: ${secretLink}`); | ||
}); | ||
window.createSecret('Some confidential information…').then(({ secretLink }) => { | ||
console.log(`Success! Your secret link is: ${secretLink}`) | ||
}) | ||
</script> | ||
@@ -97,0 +95,0 @@ </body> |
@@ -5,21 +5,14 @@ /** | ||
*/ | ||
import { | ||
createSecret, | ||
CreateSecret, | ||
retrieveSecret, | ||
RetrieveSecret, | ||
} from "./main"; | ||
import { createSecret, CreateSecret, retrieveSecret, RetrieveSecret } from './main' | ||
declare global { | ||
interface Window { | ||
createSecret: CreateSecret; | ||
retrieveSecret: RetrieveSecret; | ||
createSecret: CreateSecret | ||
retrieveSecret: RetrieveSecret | ||
} | ||
} | ||
window.createSecret = createSecret; | ||
window.retrieveSecret = retrieveSecret; | ||
window.createSecret = createSecret | ||
window.retrieveSecret = retrieveSecret | ||
console.log( | ||
'Method "createSecret" and "retrieveSecret" were added to the window object."' | ||
); | ||
console.log('Method "createSecret" and "retrieveSecret" were added to the window object."') |
@@ -1,3 +0,3 @@ | ||
export const urlAliasLength = 16; // 12 | ||
export const encryptionKeyLength = 24; // 14 | ||
export const baseUrl = "https://scrt.link"; | ||
export const urlAliasLength = 16 // 12 | ||
export const encryptionKeyLength = 24 // 14 | ||
export const baseUrl = 'https://scrt.link' |
@@ -1,37 +0,32 @@ | ||
import { | ||
createSecret, | ||
retrieveSecret, | ||
generateAlias, | ||
generateEncryptionKey, | ||
} from "./main"; | ||
import { urlAliasLength, encryptionKeyLength } from "./constants"; | ||
import { createSecret, retrieveSecret, generateAlias, generateEncryptionKey } from './main' | ||
import { urlAliasLength, encryptionKeyLength } from './constants' | ||
test("Generate alias", async () => { | ||
expect(generateAlias()).toHaveLength(urlAliasLength); | ||
}); | ||
test('Generate alias', async () => { | ||
expect(generateAlias()).toHaveLength(urlAliasLength) | ||
}) | ||
test("Generate encryption key", async () => { | ||
expect(generateEncryptionKey()).toHaveLength(encryptionKeyLength); | ||
}); | ||
test('Generate encryption key', async () => { | ||
expect(generateEncryptionKey()).toHaveLength(encryptionKeyLength) | ||
}) | ||
test("Create and retrieve secret", async () => { | ||
const secretMessage = "I love you!"; | ||
const secret = await createSecret(secretMessage); | ||
test('Create and retrieve secret', async () => { | ||
const secretMessage = 'I love you!' | ||
const secret = await createSecret(secretMessage) | ||
expect(secret).toHaveProperty("secretLink"); | ||
expect(secret).toHaveProperty("alias"); | ||
expect(secret).toHaveProperty("encryptionKey"); | ||
expect(secret).toHaveProperty('secretLink') | ||
expect(secret).toHaveProperty('alias') | ||
expect(secret).toHaveProperty('encryptionKey') | ||
const { alias, encryptionKey } = secret; | ||
const { alias, encryptionKey } = secret | ||
const { message } = await retrieveSecret(alias, encryptionKey); | ||
const { message } = await retrieveSecret(alias, encryptionKey) | ||
expect(message).toBe(secretMessage); | ||
expect(message).toBe(secretMessage) | ||
// Second attempt fails since secret is gone. | ||
try { | ||
await retrieveSecret(alias, encryptionKey); | ||
await retrieveSecret(alias, encryptionKey) | ||
} catch (error) { | ||
expect(error.message).toContain("Secret not found"); | ||
expect(error.message).toContain('Secret not found') | ||
} | ||
}); | ||
}) |
@@ -1,11 +0,5 @@ | ||
import { reject, is, isNil } from "ramda"; | ||
import { baseUrl as defaultBaseUrl } from "./constants"; | ||
import { | ||
encryptMessage, | ||
decryptMessage, | ||
generateAlias, | ||
generateEncryptionKey, | ||
api, | ||
} from "./utils"; | ||
import { CreateSecretOptions, SecretUrlFields } from "./types"; | ||
import { reject, is, isNil } from 'ramda' | ||
import { baseUrl as defaultBaseUrl } from './constants' | ||
import { encryptMessage, decryptMessage, generateAlias, generateEncryptionKey, api } from './utils' | ||
import { CreateSecretOptions, SecretUrlFields } from './types' | ||
@@ -15,8 +9,8 @@ export type CreateSecret = ( | ||
options?: CreateSecretOptions, | ||
baseUrl?: string | ||
baseUrl?: string, | ||
) => Promise<{ | ||
alias: string; | ||
encryptionKey: string; | ||
secretLink: string; | ||
}>; | ||
alias: string | ||
encryptionKey: string | ||
secretLink: string | ||
}> | ||
@@ -26,3 +20,3 @@ export const createSecret: CreateSecret = async ( | ||
options = {}, | ||
baseUrl = defaultBaseUrl | ||
baseUrl = defaultBaseUrl, | ||
) => { | ||
@@ -33,4 +27,4 @@ const { | ||
password, | ||
secretType = "text", | ||
neogramDestructionMessage = "This message will self-destruct in…", | ||
secretType = 'text', | ||
neogramDestructionMessage = 'This message will self-destruct in…', | ||
neogramDestructionTimeout = 3, | ||
@@ -40,10 +34,11 @@ receiptApi, | ||
receiptPhoneNumber, | ||
} = options; | ||
file, | ||
} = options | ||
if (password && is(String, password)) { | ||
message = encryptMessage(message, password); | ||
message = encryptMessage(message, password) | ||
} | ||
// Default encryption | ||
message = encryptMessage(message, encryptionKey); | ||
message = encryptMessage(message, encryptionKey) | ||
@@ -55,3 +50,3 @@ const data = { | ||
isEncryptedWithUserPassword: !!password, | ||
...(secretType === "neogram" | ||
...(secretType === 'neogram' | ||
? { | ||
@@ -65,14 +60,15 @@ neogramDestructionMessage, | ||
receiptPhoneNumber, | ||
}; | ||
...(secretType === 'file' | ||
? { | ||
file, | ||
} | ||
: {}), | ||
} | ||
return api( | ||
`${baseUrl}/api/secrets`, | ||
{ method: "POST" }, | ||
reject(isNil, data) | ||
).then(() => ({ | ||
return api(`${baseUrl}/api/secrets`, { method: 'POST' }, reject(isNil, data)).then(() => ({ | ||
alias, | ||
encryptionKey, | ||
secretLink: `${baseUrl}/l/${alias}#${encryptionKey}`, | ||
})); | ||
}; | ||
})) | ||
} | ||
@@ -82,4 +78,4 @@ export type RetrieveSecret = ( | ||
decryptionKey: string, | ||
baseUrl?: string | ||
) => Promise<Partial<SecretUrlFields>>; | ||
baseUrl?: string, | ||
) => Promise<Partial<SecretUrlFields>> | ||
@@ -89,22 +85,19 @@ export const retrieveSecret: RetrieveSecret = async ( | ||
decryptionKey, | ||
baseUrl = defaultBaseUrl | ||
baseUrl = defaultBaseUrl, | ||
) => { | ||
const secretRaw = await api<Partial<SecretUrlFields>>( | ||
`${baseUrl}/api/secrets/${alias}`, | ||
{ | ||
method: "DELETE", | ||
} | ||
); | ||
const secretRaw = await api<Partial<SecretUrlFields>>(`${baseUrl}/api/secrets/${alias}`, { | ||
method: 'DELETE', | ||
}) | ||
if (!secretRaw.message) { | ||
throw new Error(`Couldn't retrieve secret message.`); | ||
throw new Error(`Couldn't retrieve secret message.`) | ||
} | ||
const result = decryptMessage(secretRaw.message, decryptionKey); | ||
const result = decryptMessage(secretRaw.message, decryptionKey) | ||
if (!result) { | ||
throw new Error("Decryption failed."); | ||
throw new Error('Decryption failed.') | ||
} | ||
return { ...secretRaw, message: result }; | ||
}; | ||
return { ...secretRaw, message: result } | ||
} | ||
@@ -118,6 +111,6 @@ const ScrtLink = { | ||
generateEncryptionKey, | ||
}; | ||
} | ||
export { encryptMessage, decryptMessage, generateAlias, generateEncryptionKey }; | ||
export { encryptMessage, decryptMessage, generateAlias, generateEncryptionKey } | ||
export default ScrtLink; | ||
export default ScrtLink |
@@ -1,21 +0,25 @@ | ||
export type SecretType = "text" | "url" | "neogram"; | ||
export type SecretType = 'text' | 'url' | 'neogram' | 'file' | ||
export interface CreateSecretOptions { | ||
alias?: string; | ||
secretType?: SecretType; | ||
encryptionKey?: string; | ||
neogramDestructionMessage?: string; | ||
neogramDestructionTimeout?: number; | ||
password?: string; | ||
receiptEmail?: string; | ||
receiptPhoneNumber?: string; | ||
receiptApi?: Record<string, unknown>; | ||
alias?: string | ||
secretType?: SecretType | ||
encryptionKey?: string | ||
neogramDestructionMessage?: string | ||
neogramDestructionTimeout?: number | ||
password?: string | ||
receiptEmail?: string | ||
receiptPhoneNumber?: string | ||
receiptApi?: Record<string, unknown> | ||
file?: { | ||
bucket: string | ||
key: string | ||
type: string | ||
name: string | ||
size: number | ||
} | ||
} | ||
export type SecretUrlFields = Omit< | ||
CreateSecretOptions, | ||
"encryptionKey" | "password" | ||
> & { | ||
message: string; | ||
isEncryptedWithUserPassword: boolean; | ||
}; | ||
export type SecretUrlFields = Omit<CreateSecretOptions, 'encryptionKey' | 'password'> & { | ||
message: string | ||
isEncryptedWithUserPassword: boolean | ||
} |
@@ -1,7 +0,7 @@ | ||
import fetch from "cross-fetch"; | ||
import { AES, enc, SHA256 } from "crypto-js"; | ||
import { customAlphabet } from "nanoid"; | ||
import { nolookalikes } from "nanoid-dictionary"; | ||
import fetch from 'cross-fetch' | ||
import { AES, enc, SHA256 } from 'crypto-js' | ||
import { customAlphabet } from 'nanoid' | ||
import { nolookalikes } from 'nanoid-dictionary' | ||
import { urlAliasLength, encryptionKeyLength } from "./constants"; | ||
import { urlAliasLength, encryptionKeyLength } from './constants' | ||
@@ -11,9 +11,9 @@ export async function api<T>( | ||
options?: RequestInit, | ||
data?: Record<string, unknown> | null | ||
data?: Record<string, unknown> | null, | ||
): Promise<T> { | ||
// Default options are marked with * | ||
const response = await fetch(url, { | ||
method: "GET", // *GET, POST, PUT, DELETE, etc. | ||
method: 'GET', // *GET, POST, PUT, DELETE, etc. | ||
headers: { | ||
"Content-Type": "application/json", | ||
'Content-Type': 'application/json', | ||
...options?.headers, | ||
@@ -23,33 +23,32 @@ }, | ||
...(data ? { body: JSON.stringify(data) } : {}), // body data type must match "Content-Type" header | ||
}); | ||
}) | ||
if (!response.ok) { | ||
const errorResponse = await response.json(); | ||
throw errorResponse; | ||
const errorResponse = await response.json() | ||
throw errorResponse | ||
} | ||
return response.json() as Promise<T>; | ||
return response.json() as Promise<T> | ||
} | ||
const createHash = (key: string): string => enc.Base64.stringify(SHA256(key)); | ||
const createHash = (key: string): string => enc.Base64.stringify(SHA256(key)) | ||
type EncryptMessage = (message: string, encryptionKey: string) => string; | ||
type EncryptMessage = (message: string, encryptionKey: string) => string | ||
export const encryptMessage: EncryptMessage = (message, encryptionKey) => { | ||
const hash = createHash(encryptionKey); | ||
return AES.encrypt(message, hash).toString(); | ||
}; | ||
const hash = createHash(encryptionKey) | ||
return AES.encrypt(message, hash).toString() | ||
} | ||
type DecryptMessage = (message: string, decryptionKey: string) => string; | ||
type DecryptMessage = (message: string, decryptionKey: string) => string | ||
export const decryptMessage: DecryptMessage = (message, decryptionKey) => { | ||
const hash = createHash(decryptionKey); | ||
const bytes = AES.decrypt(message, hash); | ||
return bytes.toString(enc.Utf8); | ||
}; | ||
const hash = createHash(decryptionKey) | ||
const bytes = AES.decrypt(message, hash) | ||
return bytes.toString(enc.Utf8) | ||
} | ||
const generateNanoId = (length: number): string => { | ||
const nanoid = customAlphabet(nolookalikes, length); | ||
return nanoid(); | ||
}; | ||
export const generateAlias = (): string => generateNanoId(urlAliasLength); | ||
export const generateEncryptionKey = (): string => | ||
generateNanoId(encryptionKeyLength); | ||
const nanoid = customAlphabet(nolookalikes, length) | ||
return nanoid() | ||
} | ||
export const generateAlias = (): string => generateNanoId(urlAliasLength) | ||
export const generateEncryptionKey = (): string => generateNanoId(encryptionKeyLength) |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
1871841
39
15
1614
105
7