@casual-simulation/aux-records
Advanced tools
Comparing version 3.0.2-alpha.1884665520 to 3.0.5-alpha.2222886363
@@ -28,3 +28,10 @@ import { NotLoggedInError, ServerError } from './Errors'; | ||
listData(recordName: string, address: string | null): Promise<ListDataResult>; | ||
eraseData(recordKey: string, address: string): Promise<EraseDataResult>; | ||
/** | ||
* Erases the data in the given record and address. | ||
* Uses the given record key to access the record and the given subject ID to determine if the user is allowed to access the record. | ||
* @param recordKey The key that should be used to access the record. | ||
* @param address The address that the record should be deleted from. | ||
* @param subjectId THe ID of the user that this request came from. | ||
*/ | ||
eraseData(recordKey: string, address: string, subjectId: string): Promise<EraseDataResult>; | ||
} | ||
@@ -78,3 +85,3 @@ export declare type RecordDataResult = RecordDataSuccess | RecordDataFailure; | ||
success: false; | ||
errorCode: ServerError | EraseDataStoreResult['errorCode'] | ValidatePublicRecordKeyFailure['errorCode']; | ||
errorCode: ServerError | NotLoggedInError | EraseDataStoreResult['errorCode'] | ValidatePublicRecordKeyFailure['errorCode']; | ||
errorMessage: string; | ||
@@ -81,0 +88,0 @@ } |
@@ -43,2 +43,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
} | ||
if (!subjectId && result.policy !== 'subjectless') { | ||
return { | ||
success: false, | ||
errorCode: 'not_logged_in', | ||
errorMessage: 'The user must be logged in in order to record data.', | ||
}; | ||
} | ||
if (result.policy === 'subjectless') { | ||
subjectId = null; | ||
} | ||
const recordName = result.recordName; | ||
@@ -113,3 +123,10 @@ const result2 = yield this._store.setData(recordName, address, data, result.ownerId, subjectId); | ||
} | ||
eraseData(recordKey, address) { | ||
/** | ||
* Erases the data in the given record and address. | ||
* Uses the given record key to access the record and the given subject ID to determine if the user is allowed to access the record. | ||
* @param recordKey The key that should be used to access the record. | ||
* @param address The address that the record should be deleted from. | ||
* @param subjectId THe ID of the user that this request came from. | ||
*/ | ||
eraseData(recordKey, address, subjectId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -125,2 +142,9 @@ try { | ||
} | ||
if (!subjectId && result.policy !== 'subjectless') { | ||
return { | ||
success: false, | ||
errorCode: 'not_logged_in', | ||
errorMessage: 'The user must be logged in in order to record data.', | ||
}; | ||
} | ||
const recordName = result.recordName; | ||
@@ -127,0 +151,0 @@ const result2 = yield this._store.eraseData(recordName, address); |
@@ -21,4 +21,5 @@ import { NotLoggedInError, ServerError } from './Errors'; | ||
* @param count The number of events to add/subtract. | ||
* @param subjectId The ID of the user that is adding the count. | ||
*/ | ||
addCount(recordKey: string, eventName: string, count: number): Promise<AddCountResult>; | ||
addCount(recordKey: string, eventName: string, count: number, subjectId: string): Promise<AddCountResult>; | ||
/** | ||
@@ -25,0 +26,0 @@ * Gets the current count of events with the given name from the given record. |
@@ -28,4 +28,5 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
* @param count The number of events to add/subtract. | ||
* @param subjectId The ID of the user that is adding the count. | ||
*/ | ||
addCount(recordKey, eventName, count) { | ||
addCount(recordKey, eventName, count, subjectId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -41,2 +42,9 @@ try { | ||
} | ||
if (!subjectId && result.policy !== 'subjectless') { | ||
return { | ||
success: false, | ||
errorCode: 'not_logged_in', | ||
errorMessage: 'The user must be logged in in order to record events.', | ||
}; | ||
} | ||
const recordName = result.recordName; | ||
@@ -43,0 +51,0 @@ const result2 = yield this._store.addEventCount(recordName, eventName, count); |
@@ -12,3 +12,9 @@ import { FileRecordsStore, AddFileFailure, MarkFileRecordAsUploadedFailure, EraseFileStoreResult } from './FileRecordsStore'; | ||
recordFile(recordKey: string, userId: string, request: RecordFileRequest): Promise<RecordFileResult>; | ||
eraseFile(recordKey: string, fileName: string): Promise<EraseFileResult>; | ||
/** | ||
* Attempts to erase the given file using the given record key and subject. | ||
* @param recordKey The key that should be used to erase the file. | ||
* @param fileName The name of the file. | ||
* @param subjectId The ID of the user that is making this request. | ||
*/ | ||
eraseFile(recordKey: string, fileName: string, subjectId: string): Promise<EraseFileResult>; | ||
markFileAsUploaded(recordName: string, fileName: string): Promise<FileUploadedResult>; | ||
@@ -15,0 +21,0 @@ } |
@@ -26,2 +26,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
} | ||
if (!userId && keyResult.policy !== 'subjectless') { | ||
return { | ||
success: false, | ||
errorCode: 'not_logged_in', | ||
errorMessage: 'The user must be logged in in order to record files.', | ||
}; | ||
} | ||
if (keyResult.policy === 'subjectless') { | ||
userId = null; | ||
} | ||
const publisherId = keyResult.ownerId; | ||
@@ -95,3 +105,9 @@ const recordName = keyResult.recordName; | ||
} | ||
eraseFile(recordKey, fileName) { | ||
/** | ||
* Attempts to erase the given file using the given record key and subject. | ||
* @param recordKey The key that should be used to erase the file. | ||
* @param fileName The name of the file. | ||
* @param subjectId The ID of the user that is making this request. | ||
*/ | ||
eraseFile(recordKey, fileName, subjectId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -103,2 +119,9 @@ try { | ||
} | ||
if (!subjectId && keyResult.policy !== 'subjectless') { | ||
return { | ||
success: false, | ||
errorCode: 'not_logged_in', | ||
errorMessage: 'The user must be logged in in order to erase files.', | ||
}; | ||
} | ||
const publisherId = keyResult.ownerId; | ||
@@ -105,0 +128,0 @@ const recordName = keyResult.recordName; |
@@ -1,8 +0,12 @@ | ||
import { Record, RecordsStore } from './RecordsStore'; | ||
import { Record, RecordKey, RecordsStore } from './RecordsStore'; | ||
export declare class MemoryRecordsStore implements RecordsStore { | ||
private _records; | ||
private _recordKeys; | ||
get recordKeys(): RecordKey[]; | ||
getRecordByName(name: string): Promise<Record>; | ||
updateRecord(record: Record): Promise<void>; | ||
addRecord(record: Record): Promise<void>; | ||
addRecordKey(key: RecordKey): Promise<void>; | ||
getRecordKeyByRecordAndHash(recordName: string, hash: string): Promise<RecordKey>; | ||
} | ||
//# sourceMappingURL=MemoryRecordsStore.d.ts.map |
@@ -13,3 +13,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
this._records = []; | ||
this._recordKeys = []; | ||
} | ||
get recordKeys() { | ||
return this._recordKeys; | ||
} | ||
getRecordByName(name) { | ||
@@ -37,3 +41,17 @@ return __awaiter(this, void 0, void 0, function* () { | ||
} | ||
addRecordKey(key) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const existingKeyIndex = this._recordKeys.findIndex((k) => k.recordName === key.recordName && k.secretHash === key.secretHash); | ||
if (existingKeyIndex < 0) { | ||
this._recordKeys.push(key); | ||
} | ||
}); | ||
} | ||
getRecordKeyByRecordAndHash(recordName, hash) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const key = this._recordKeys.find(k => k.recordName === recordName && k.secretHash == hash); | ||
return key; | ||
}); | ||
} | ||
} | ||
//# sourceMappingURL=MemoryRecordsStore.js.map |
{ | ||
"name": "@casual-simulation/aux-records", | ||
"version": "3.0.2-alpha.1884665520", | ||
"version": "3.0.5-alpha.2222886363", | ||
"description": "Helpers and managers used by the CasualOS records system.", | ||
@@ -41,6 +41,6 @@ "keywords": [], | ||
"dependencies": { | ||
"@casual-simulation/crypto": "^3.0.2-alpha.1884665520", | ||
"@casual-simulation/crypto": "^3.0.0", | ||
"tweetnacl": "1.0.3" | ||
}, | ||
"gitHead": "6d11ea6fee9d68f1ddbd0f049df764aafcc4971d" | ||
"gitHead": "c925426ee76b4b2d1acb9ec365b008527fb06f66" | ||
} |
@@ -1,3 +0,3 @@ | ||
import { RecordsStore } from './RecordsStore'; | ||
import { ServerError } from './Errors'; | ||
import { PublicRecordKeyPolicy, RecordsStore } from './RecordsStore'; | ||
import { NotLoggedInError, ServerError } from './Errors'; | ||
/** | ||
@@ -12,6 +12,7 @@ * Defines a class that manages records and their keys. | ||
* @param name The name of the record. | ||
* @param policy The policy that should be used for the public record key. | ||
* @param userId The ID of the user that is creating the public record. | ||
* @returns | ||
*/ | ||
createPublicRecordKey(name: string, userId: string): Promise<CreatePublicRecordKeyResult>; | ||
createPublicRecordKey(name: string, policy: PublicRecordKeyPolicy, userId: string): Promise<CreatePublicRecordKeyResult>; | ||
/** | ||
@@ -38,2 +39,6 @@ * Validates the given record key. Returns success if the key is valid and can be used to publish things to its bucket. | ||
ownerId: string; | ||
/** | ||
* The policy for the record key. | ||
*/ | ||
policy: PublicRecordKeyPolicy; | ||
} | ||
@@ -89,3 +94,3 @@ /** | ||
*/ | ||
errorCode: UnauthorizedToCreateRecordKeyError | ServerError | 'not_supported'; | ||
errorCode: UnauthorizedToCreateRecordKeyError | NotLoggedInError | 'invalid_policy' | ServerError | 'not_supported'; | ||
/** | ||
@@ -106,2 +111,6 @@ * The error message. | ||
/** | ||
* The default policy for keys that do not have a specified record key. | ||
*/ | ||
export declare const DEFAULT_RECORD_KEY_POLICY: PublicRecordKeyPolicy; | ||
/** | ||
* Formats the given record name and record secret into a record key. | ||
@@ -111,11 +120,29 @@ * @param recordName The name of the record. | ||
*/ | ||
export declare function formatRecordKey(recordName: string, recordSecret: string): string; | ||
export declare function formatV1RecordKey(recordName: string, recordSecret: string): string; | ||
/** | ||
* Formats the given record name and record secret into a record key. | ||
* @param recordName The name of the record. | ||
* @param recordSecret The secret that is used to access the record. | ||
* @param keyPolicy The policy that the key uses. | ||
*/ | ||
export declare function formatV2RecordKey(recordName: string, recordSecret: string, keyPolicy: PublicRecordKeyPolicy): string; | ||
/** | ||
* Parses the given record key into a name and password pair. | ||
* Returns null if the key cannot be parsed. | ||
* @param key The key to parse. | ||
* @returns | ||
*/ | ||
export declare function parseRecordKey(key: string): [name: string, password: string]; | ||
export declare function parseRecordKey(key: string): [name: string, password: string, policy: PublicRecordKeyPolicy]; | ||
/** | ||
* Parses a version 2 record key into a name, password, and policy trio. | ||
* Returns null if the key cannot be parsed or if it is not a V2 key. | ||
* @param key The key to parse. | ||
*/ | ||
export declare function parseV2RecordKey(key: string): [name: string, password: string, policy: PublicRecordKeyPolicy]; | ||
/** | ||
* Parses a version 1 record key into a name and password pair. | ||
* Returns null if the key cannot be parsed or if it is not a V1 key. | ||
* @param key The key to parse. | ||
*/ | ||
export declare function parseV1RecordKey(key: string): [name: string, password: string, policy: PublicRecordKeyPolicy]; | ||
/** | ||
* Determines if the given value is a record key. | ||
@@ -122,0 +149,0 @@ * @param key The value to check. |
@@ -11,3 +11,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { toBase64String, fromBase64String } from './Utils'; | ||
import { hashPasswordWithSalt, verifyPasswordAgainstHashes, } from '@casual-simulation/crypto'; | ||
import { hashPasswordWithSalt, } from '@casual-simulation/crypto'; | ||
import { randomBytes } from 'tweetnacl'; | ||
@@ -25,9 +25,24 @@ import { fromByteArray } from 'base64-js'; | ||
* @param name The name of the record. | ||
* @param policy The policy that should be used for the public record key. | ||
* @param userId The ID of the user that is creating the public record. | ||
* @returns | ||
*/ | ||
createPublicRecordKey(name, userId) { | ||
createPublicRecordKey(name, policy, userId) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
try { | ||
if (!userId) { | ||
return { | ||
success: false, | ||
errorCode: 'not_logged_in', | ||
errorMessage: 'The user must be logged in in order to create a record key.' | ||
}; | ||
} | ||
const record = yield this._store.getRecordByName(name); | ||
if (!!policy && policy !== 'subjectfull' && policy !== 'subjectless') { | ||
return { | ||
success: false, | ||
errorCode: 'invalid_policy', | ||
errorMessage: 'The record key policy must be either "subjectfull" or "subjectless".' | ||
}; | ||
} | ||
if (record) { | ||
@@ -45,6 +60,11 @@ if (record.ownerId !== userId) { | ||
const passwordHash = hashPasswordWithSalt(password, salt); | ||
yield this._store.updateRecord(Object.assign(Object.assign({}, record), { secretHashes: [...record.secretHashes, passwordHash] })); | ||
yield this._store.addRecordKey({ | ||
recordName: name, | ||
secretHash: passwordHash, | ||
policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY, | ||
creatorId: record.ownerId, | ||
}); | ||
return { | ||
success: true, | ||
recordKey: formatRecordKey(name, password), | ||
recordKey: formatV2RecordKey(name, password, policy), | ||
recordName: name, | ||
@@ -61,8 +81,14 @@ }; | ||
ownerId: userId, | ||
secretHashes: [passwordHash], | ||
secretHashes: [], | ||
secretSalt: salt, | ||
}); | ||
yield this._store.addRecordKey({ | ||
recordName: name, | ||
secretHash: passwordHash, | ||
policy: policy !== null && policy !== void 0 ? policy : DEFAULT_RECORD_KEY_POLICY, | ||
creatorId: userId, | ||
}); | ||
return { | ||
success: true, | ||
recordKey: formatRecordKey(name, password), | ||
recordKey: formatV2RecordKey(name, password, policy), | ||
recordName: name, | ||
@@ -98,3 +124,3 @@ }; | ||
} | ||
const [name, password] = parseResult; | ||
const [name, password, policy] = parseResult; | ||
const record = yield this._store.getRecordByName(name); | ||
@@ -108,7 +134,27 @@ if (!record) { | ||
} | ||
const result = verifyPasswordAgainstHashes(password, record.secretSalt, record.secretHashes); | ||
if (result) { | ||
const hash = hashPasswordWithSalt(password, record.secretSalt); | ||
let valid = false; | ||
let resultPolicy = DEFAULT_RECORD_KEY_POLICY; | ||
if (record.secretHashes.some(h => h === hash)) { | ||
valid = true; | ||
} | ||
else { | ||
const key = yield this._store.getRecordKeyByRecordAndHash(name, hash); | ||
if (!!key) { | ||
resultPolicy = key.policy; | ||
valid = true; | ||
} | ||
} | ||
if (resultPolicy !== policy) { | ||
return { | ||
success: false, | ||
errorCode: 'invalid_record_key', | ||
errorMessage: 'Invalid record key.' | ||
}; | ||
} | ||
if (valid) { | ||
return { | ||
success: true, | ||
recordName: name, | ||
policy: policy, | ||
ownerId: record.ownerId, | ||
@@ -137,2 +183,6 @@ }; | ||
/** | ||
* The default policy for keys that do not have a specified record key. | ||
*/ | ||
export const DEFAULT_RECORD_KEY_POLICY = 'subjectfull'; | ||
/** | ||
* Formats the given record name and record secret into a record key. | ||
@@ -142,15 +192,75 @@ * @param recordName The name of the record. | ||
*/ | ||
export function formatRecordKey(recordName, recordSecret) { | ||
export function formatV1RecordKey(recordName, recordSecret) { | ||
return `vRK1.${toBase64String(recordName)}.${toBase64String(recordSecret)}`; | ||
} | ||
/** | ||
* Formats the given record name and record secret into a record key. | ||
* @param recordName The name of the record. | ||
* @param recordSecret The secret that is used to access the record. | ||
* @param keyPolicy The policy that the key uses. | ||
*/ | ||
export function formatV2RecordKey(recordName, recordSecret, keyPolicy) { | ||
return `vRK2.${toBase64String(recordName)}.${toBase64String(recordSecret)}.${keyPolicy !== null && keyPolicy !== void 0 ? keyPolicy : DEFAULT_RECORD_KEY_POLICY}`; | ||
} | ||
/** | ||
* Parses the given record key into a name and password pair. | ||
* Returns null if the key cannot be parsed. | ||
* @param key The key to parse. | ||
* @returns | ||
*/ | ||
export function parseRecordKey(key) { | ||
var _a; | ||
return (_a = parseV2RecordKey(key)) !== null && _a !== void 0 ? _a : parseV1RecordKey(key); | ||
} | ||
/** | ||
* Parses a version 2 record key into a name, password, and policy trio. | ||
* Returns null if the key cannot be parsed or if it is not a V2 key. | ||
* @param key The key to parse. | ||
*/ | ||
export function parseV2RecordKey(key) { | ||
if (!key) { | ||
return null; | ||
} | ||
if (!key.startsWith('vRK2.')) { | ||
return null; | ||
} | ||
const withoutVersion = key.slice('vRK2.'.length); | ||
let periodAfterName = withoutVersion.indexOf('.'); | ||
if (periodAfterName < 0) { | ||
return null; | ||
} | ||
const nameBase64 = withoutVersion.slice(0, periodAfterName); | ||
const passwordPlusPolicy = withoutVersion.slice(periodAfterName + 1); | ||
if (nameBase64.length <= 0 || passwordPlusPolicy.length <= 0) { | ||
return null; | ||
} | ||
const periodAfterPassword = passwordPlusPolicy.indexOf('.'); | ||
if (periodAfterPassword < 0) { | ||
return null; | ||
} | ||
const passwordBase64 = passwordPlusPolicy.slice(0, periodAfterPassword); | ||
const policy = passwordPlusPolicy.slice(periodAfterPassword + 1); | ||
if (passwordBase64.length <= 0 || policy.length <= 0) { | ||
return null; | ||
} | ||
if (policy !== 'subjectfull' && policy !== 'subjectless') { | ||
return null; | ||
} | ||
try { | ||
const name = fromBase64String(nameBase64); | ||
const password = fromBase64String(passwordBase64); | ||
return [name, password, policy]; | ||
} | ||
catch (err) { | ||
return null; | ||
} | ||
} | ||
/** | ||
* Parses a version 1 record key into a name and password pair. | ||
* Returns null if the key cannot be parsed or if it is not a V1 key. | ||
* @param key The key to parse. | ||
*/ | ||
export function parseV1RecordKey(key) { | ||
if (!key) { | ||
return null; | ||
} | ||
if (!key.startsWith('vRK1.')) { | ||
@@ -172,3 +282,3 @@ return null; | ||
const password = fromBase64String(passwordBase64); | ||
return [name, password]; | ||
return [name, password, DEFAULT_RECORD_KEY_POLICY]; | ||
} | ||
@@ -175,0 +285,0 @@ catch (err) { |
@@ -20,2 +20,13 @@ /** | ||
getRecordByName(name: string): Promise<Record>; | ||
/** | ||
* Adds the given record key to the store. | ||
* @param key The key to add. | ||
*/ | ||
addRecordKey(key: RecordKey): Promise<void>; | ||
/** | ||
* Gets the record key for the given record name that has the given hash. | ||
* @param recordName The name of the record. | ||
* @param hash The scrypt hash of the key that should be retrieved. | ||
*/ | ||
getRecordKeyByRecordAndHash(recordName: string, hash: string): Promise<RecordKey>; | ||
} | ||
@@ -46,2 +57,30 @@ /** | ||
} | ||
/** | ||
* Defines a type that represents the different kinds of policies that a record key can have. | ||
* | ||
* - null and "subjectfull" indicate that actions performed with this key must require a subject to provide their access token in order for operations to succeed. | ||
* - "subjectless" indicates that actions may be performed with key despite not having an access key from a subject. | ||
*/ | ||
export declare type PublicRecordKeyPolicy = null | 'subjectfull' | 'subjectless'; | ||
/** | ||
* Defines an interface for record key objects. | ||
*/ | ||
export interface RecordKey { | ||
/** | ||
* The name of the record that the key is for. | ||
*/ | ||
recordName: string; | ||
/** | ||
* The scrypt hash of the secret that this key is for. | ||
*/ | ||
secretHash: string; | ||
/** | ||
* The policy that the key uses. | ||
*/ | ||
policy: PublicRecordKeyPolicy; | ||
/** | ||
* The ID of the user that created this key. | ||
*/ | ||
creatorId: string; | ||
} | ||
//# sourceMappingURL=RecordsStore.d.ts.map |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
115870
2123