├── .babelrc ├── .gitignore ├── README.md ├── jest.config.js ├── lerna.json ├── package.json ├── packages └── secure-id │ ├── .npmignore │ ├── package.json │ ├── src │ ├── IDMailformedError.ts │ ├── SecID.spec.ts │ ├── SecID.ts │ ├── base64.ts │ └── index.ts │ └── tsconfig.json ├── tsconfig.json ├── tsconfig.settings.json ├── tslint.json ├── yarn-error.log └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["@babel/plugin-syntax-dynamic-import", "@babel/plugin-transform-modules-commonjs"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | lib 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @openland/secure-id 2 | `@openland/secure-id` is a library for encrypting id's from database to make them safe to use in API calls. 3 | 4 | ## How to use 5 | ``` 6 | yarn install @openland/secure-id 7 | ``` 8 | 9 | Before using your library you need to create a *Shared Secret* and distributed it to every application node that is going to use this library. Using of OpenSSH to generate long random string is highly recommended. Shared Secret MUST NOT be stored in database, use configuration files or environment variables instead. 10 | Shared Secret MUST be stored in a safe place since it could NOT be changed. 11 | 12 | ```typescript 13 | 14 | // First you need to create a factory for IDs 15 | // WARNING: Constructor performs heavy computations and can take up to 100ms. Always reuse factory and IDs! 16 | let factory = new SecIDFactory('Shared Secret'); 17 | 18 | // Then declare all IDs: 19 | let userId = factory.createId('UserEntity'); 20 | 21 | // Encrypt and decrypt 22 | let encryptedUserId = userId.serialize(1); // == "AVljNZ9dX8I40v3xRMeVUyN9Nv" 23 | let decryptedUserId = userId.parse(encryptedUserId); // == 1 24 | ``` 25 | 26 | ## ID style 27 | `@openland/secure-id` supports three encoding formats: `hex`, `base64` and `hashids`. Default one is `hashids`: https://hashids.org. 28 | 29 | ```typescript 30 | 31 | let hexFactory = new SecIDFactory('Shared Secret', 'hex'); 32 | let base64Factory = new SecIDFactory('Shared Secret', 'base64'); 33 | ``` 34 | 35 | 36 | ## License 37 | MIT (c) Data Makes Perfect LLC 38 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | transform: { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | moduleFileExtensions: [ 7 | 'ts', 8 | 'tsx', 9 | 'js', 10 | 'jsx', 11 | 'json', 12 | 'node' 13 | ], 14 | testRegex: '.*\\.spec\\.tsx?$', 15 | testPathIgnorePatterns: ['/node_modules/', '/build/', '/dist/'], 16 | coverageDirectory: 'coverage', 17 | collectCoverageFrom: [ 18 | 'packages/**/*.{ts,tsx,js,jsx}', 19 | '!packages/**/*.d.ts', 20 | ], 21 | moduleDirectories: [ 22 | '.', 23 | 'packages', 24 | 'node_modules' 25 | ], 26 | moduleNameMapper: { 27 | 28 | // 29 | // WARNING: ORDER MATTERS 30 | // 31 | '@openland/secure-id/(.*)': '/packages/secure-id/$1', 32 | '@openland/secure-id': '/packages/secure-id' 33 | }, 34 | }; -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.2.0", 3 | "packages": [ 4 | "packages/*" 5 | ], 6 | "npmClient": "yarn", 7 | "useWorkspaces": true, 8 | "version": "0.1.2" 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": { 4 | "packages": [ 5 | "packages/*" 6 | ] 7 | }, 8 | "scripts": { 9 | "build": "yarn tsc --build", 10 | "test": "yarn jest", 11 | "release": "yarn build && yarn test && yarn lerna publish --force-publish" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^24.0.13", 15 | "@types/node": "^12.0.4", 16 | "awesome-typescript-loader": "^5.2.1", 17 | "babel-loader": "^8.0.4", 18 | "babel-preset-es2015": "^6.24.1", 19 | "jest": "^23.6.0", 20 | "lerna": "^3.6.0", 21 | "ts-jest": "^23.10.5", 22 | "tslint": "^5.11.0", 23 | "tslint-config-prettier": "^1.17.0", 24 | "tslint-react": "^3.6.0", 25 | "typescript": "^3.2.2" 26 | }, 27 | "dependencies": { 28 | "@types/hashids": "^1.0.30", 29 | "hashids": "^1.2.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/secure-id/.npmignore: -------------------------------------------------------------------------------- 1 | src/ -------------------------------------------------------------------------------- /packages/secure-id/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@openland/secure-id", 3 | "description": "Library for creating encrypted ids that is safe to use in public facing API", 4 | "version": "0.1.2", 5 | "main": "lib/index.js", 6 | "typings": "lib/index.d.ts", 7 | "repository": "https://github.com/openland/secure-id", 8 | "author": "Steve Kite ", 9 | "license": "MIT", 10 | "gitHead": "c697113adf1cb4b345de8d545d09c6b3f002792b", 11 | "dependencies": { 12 | "hashids": "^1.2.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/secure-id/src/IDMailformedError.ts: -------------------------------------------------------------------------------- 1 | export class IDMailformedError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | } 5 | } -------------------------------------------------------------------------------- /packages/secure-id/src/SecID.spec.ts: -------------------------------------------------------------------------------- 1 | import { SecIDFactory } from './SecID'; 2 | 3 | describe('SecID Module', () => { 4 | it('Factory should work', () => { 5 | let factory = new SecIDFactory('Shared Secret'); 6 | factory.createId('First Type'); 7 | factory.createId('Second Type'); 8 | }); 9 | it('Should serialize correctly', () => { 10 | let factory = new SecIDFactory('Shared Secret', 'hex'); 11 | let id = factory.createId('IdType'); 12 | let serialized = id.serialize(123); 13 | let parsed = id.parse(serialized); 14 | expect(parsed).toEqual(123); 15 | expect(serialized).toEqual('7e1f9518ed46bbb81e74366583b6d0'); 16 | let stringId = factory.createStringId('IdType2'); 17 | serialized = stringId.serialize('test'); 18 | let parsed2 = stringId.parse(serialized); 19 | expect(parsed2).toEqual('test'); 20 | expect(serialized).toEqual('7d225718e932a56b5ecc8283b6d7bce593'); 21 | expect(stringId.parse(stringId.serialize(''))); 22 | }); 23 | it('Should serialize non-ASCII text correctly', () => { 24 | let factory = new SecIDFactory('Shared Secret', 'hex'); 25 | let id = factory.createStringId('IdType'); 26 | let serialized = id.serialize('тест🤔'); 27 | let parsed = id.parse(serialized); 28 | expect(parsed).toEqual('тест🤔'); 29 | expect(serialized).toEqual('7d1f9518e19742c89fc1f3f64aeee4599a14ba66c30d03bb48'); 30 | }); 31 | it('Should handle crash on collisions', () => { 32 | let factory = new SecIDFactory('Shared Secret', 'hex'); 33 | factory.createId('idtype'); 34 | expect(() => factory.createId('idtype')).toThrow(); 35 | }); 36 | it('should not depend on capitalization of type', () => { 37 | let factory1 = new SecIDFactory('Shared Secret', 'hex'); 38 | let factory2 = new SecIDFactory('Shared Secret', 'hex'); 39 | let type1 = factory1.createId('type'); 40 | let type2 = factory2.createId('TYPE'); 41 | expect(type1.serialize(123)).toBe(type2.serialize(123)); 42 | }); 43 | it('Serialization should be consistent', () => { 44 | let factory = new SecIDFactory('Shared Secret', 'hex'); 45 | let id = factory.createId('idtype'); 46 | let main = id.serialize(123); 47 | for (let i = 0; i < 10; i++) { 48 | expect(id.serialize(123)).toEqual(main); 49 | } 50 | }); 51 | it('should serialize differently for different types', () => { 52 | let factory = new SecIDFactory('Shared Secret', 'hex'); 53 | let type1 = factory.createId('type1'); 54 | let type2 = factory.createId('type2'); 55 | expect(type1.serialize(123)).not.toBe(type2.serialize(123)); 56 | }); 57 | it('should crash on negative numbers', () => { 58 | let factory = new SecIDFactory('Shared Secret', 'hex'); 59 | let type1 = factory.createId('type1'); 60 | expect(() => type1.serialize(-1)).toThrow('Ids can\'t be negative!'); 61 | }); 62 | 63 | it('should crash on float numbers', () => { 64 | let factory = new SecIDFactory('Shared Secret', 'hex'); 65 | let type1 = factory.createId('type1'); 66 | expect(() => type1.serialize(0.1)).toThrow('Ids can\'t be float numbers!'); 67 | }); 68 | it('should crash on incorrect input', () => { 69 | let factory = new SecIDFactory('Shared Secret', 'hex'); 70 | let type1 = factory.createId('type1'); 71 | expect(() => type1.parse('somestring')).toThrow('Invalid id'); 72 | expect(() => type1.parse('7e1f9518ed46bbb81e74366583b6d0')).toThrow('Invalid id'); 73 | }); 74 | 75 | it('should resolve type', () => { 76 | let factory = new SecIDFactory('Shared Secret', 'hex'); 77 | let type1 = factory.createId('type1'); 78 | factory.createId('type2'); 79 | factory.createId('type3'); 80 | factory.createId('type4'); 81 | let res = factory.resolve(type1.serialize(123)); 82 | expect(res.id).toEqual(123); 83 | expect(res.type).toEqual(type1); 84 | 85 | let type7 = factory.createStringId('type7'); 86 | res = factory.resolve(type7.serialize('123')); 87 | expect(res.id).toEqual('123'); 88 | expect(res.type).toEqual(type7); 89 | }); 90 | 91 | it('should handle large numbers', () => { 92 | let factory = new SecIDFactory('Shared Secret', 'hex'); 93 | let type1 = factory.createId('type1'); 94 | expect(type1.parse(type1.serialize(2147483647))).toEqual(2147483647); 95 | }); 96 | it('should crash for too large numbers', () => { 97 | let factory = new SecIDFactory('Shared Secret', 'hex'); 98 | let type1 = factory.createId('type1'); 99 | expect(() => type1.serialize(2147483648)).toThrow(); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /packages/secure-id/src/SecID.ts: -------------------------------------------------------------------------------- 1 | import * as Crypto from 'crypto'; 2 | import Hashids from 'hashids'; 3 | import { decodeBuffer, encodeBuffer } from './base64'; 4 | import { IDMailformedError } from './IDMailformedError'; 5 | 6 | // Randomly generated string for using as salt for type name hashing 7 | const typeKeySalt = '2773246209f10fc3381f5ca55c67dac5486e27ff1ce3f698b1859008fe0053e3'; 8 | // Randomly generated string for using as salt for encryption key derivation 9 | const encryptionKeySalt = 'a638abdfb70e39476858543b3216b23ca5d1ac773eaf797a130639a76081c3aa'; 10 | // Randomly generated string for using as salt for encryption iv derivation 11 | const encryptionIvSalt = '4c66c9e004fb48caaa38aa72dc749f946d0ccfe4edf8f993776388b6349a2895'; 12 | // Randomly generated string for using as salt for hmac secret derivation 13 | const hmacSecretSalt = 'c15c63b812d78d8e368f2d702e43dd885f3bcf0e446203951b12cf3ab9715716'; 14 | // Randomly generated string for using as salt for hashds salt derivation 15 | const hashidsSalt = '11705939e5cad46fa04a6fc838a3fa25c0f50439c946101199b8506ff73a2ebe'; 16 | 17 | // Truncated size of HMAC 18 | const HMAC_LENGTH = 8; 19 | // Expected minimum Key Length 20 | const MIN_KEY_LENGTH = 13; 21 | 22 | type SecIDv2ValueTypeName = 'number' | 'string'; 23 | type SecIDv2ValueType = number | string; 24 | 25 | const NUMBER_VERSION = 1; 26 | const STRING_VERSION = 2; 27 | 28 | export type SecIDStyle = 'hex' | 'base64' | 'hashids'; 29 | 30 | function decodeStyle(value: string, style: SecIDStyle, hashids: Hashids) { 31 | if (style === 'hex') { 32 | return Buffer.from(value, 'hex'); 33 | } else if (style === 'base64') { 34 | return decodeBuffer(value); 35 | } else { 36 | let hid = hashids.decodeHex(value); 37 | return Buffer.from(hid, 'hex'); 38 | } 39 | } 40 | 41 | function encodeStyle(value: Buffer, style: SecIDStyle, hashids: Hashids) { 42 | if (style === 'hex') { 43 | return value.toString('hex'); 44 | } else if (style === 'base64') { 45 | return encodeBuffer(value); 46 | } else { 47 | return hashids.encodeHex(value.toString('hex')); 48 | } 49 | } 50 | 51 | function encodeNumberIdBody(value: SecIDv2ValueType, typeId: number) { 52 | // Preflight check 53 | if (typeof value !== 'number') { 54 | throw new IDMailformedError('Id value and valueType mismatch, got: ' + value + ', ' + (typeof value)); 55 | } 56 | if (value < 0) { 57 | throw new IDMailformedError('Ids can\'t be negative!'); 58 | } 59 | if (!Number.isInteger(value)) { 60 | throw new IDMailformedError('Ids can\'t be float numbers!'); 61 | } 62 | if (value > 2147483647) { 63 | throw new IDMailformedError('Ids can\'t be bigger than 2147483647. Got: ' + value); 64 | } 65 | 66 | let buf = Buffer.alloc(7); 67 | // Write version 68 | buf.writeInt8(NUMBER_VERSION, 0); 69 | // Write type id 70 | buf.writeUInt16BE(typeId, 1); 71 | // Write id 72 | buf.writeInt32BE(value, 3); 73 | 74 | return buf; 75 | } 76 | 77 | function encodeStringIdBody(value: SecIDv2ValueType, typeId: number) { 78 | // Preflight check 79 | if (typeof value !== 'string') { 80 | throw new IDMailformedError('Id value and valueType mismatch'); 81 | } 82 | if (value.length > 65535) { 83 | throw new IDMailformedError('Ids string value length can\'t be bigger than 65535. Got: ' + value.length); 84 | } 85 | 86 | let stringBuf = Buffer.from(value, 'utf-8'); 87 | let buf = Buffer.alloc(5); 88 | // Write version 89 | buf.writeInt8(STRING_VERSION, 0); 90 | // Write type id 91 | buf.writeUInt16BE(typeId, 1); 92 | // Write string length 93 | buf.writeUInt16BE(stringBuf.byteLength, 3); 94 | // Write string 95 | buf = Buffer.concat([buf, stringBuf]); 96 | 97 | return buf; 98 | } 99 | 100 | function encrypt(value: SecIDv2ValueType, valueType: SecIDv2ValueTypeName, typeId: number, encryptionKey: Buffer, encryptionIv: Buffer, hmacKey: Buffer) { 101 | let buf: Buffer; 102 | 103 | if (valueType === 'number') { 104 | buf = encodeNumberIdBody(value, typeId); 105 | } else if (valueType === 'string') { 106 | buf = encodeStringIdBody(value, typeId); 107 | } else { 108 | throw new IDMailformedError('Unknown id value type ' + valueType); 109 | } 110 | 111 | // Encrypt 112 | let cipher = Crypto.createCipheriv('aes-128-ctr', encryptionKey, encryptionIv); 113 | let res = cipher.update(buf); 114 | res = Buffer.concat([res, cipher.final()]); 115 | 116 | // then MAC 117 | let hmac = Crypto.createHmac('sha256', hmacKey).update(res).digest().slice(0, HMAC_LENGTH); 118 | res = Buffer.concat([res, hmac]); 119 | 120 | return res; 121 | } 122 | 123 | function decrypt(valuestr: string, value: Buffer, type: number | Set, encryptionKey: Buffer, encryptionIv: Buffer, hmacKey: Buffer) { 124 | let decipher = Crypto.createDecipheriv('aes-128-ctr', encryptionKey, encryptionIv); 125 | let dataLen = value.byteLength - 8; 126 | let sourceContent = value.slice(0, dataLen); 127 | let sourceHmac = value.slice(dataLen, dataLen + 8); 128 | 129 | // Decryption 130 | let decoded = decipher.update(sourceContent); 131 | decoded = Buffer.concat([decoded, decipher.final()]); 132 | 133 | // Hmac 134 | let hmacActual = Crypto.createHmac('sha256', hmacKey).update(sourceContent).digest().slice(0, HMAC_LENGTH); 135 | 136 | // For consant time read evertyhing before checking 137 | if (hmacActual.byteLength !== sourceHmac.byteLength) { 138 | if (hmacActual.length > sourceHmac.length) { 139 | sourceHmac = Buffer.concat([sourceHmac, Buffer.alloc(hmacActual.length - sourceHmac.length)]); 140 | } else { 141 | hmacActual = Buffer.concat([hmacActual, Buffer.alloc(sourceHmac.length - hmacActual.length)]); 142 | } 143 | } 144 | let hmacCorrect = Crypto.timingSafeEqual(hmacActual, sourceHmac); 145 | let valueVersion = decoded.readUInt8(0); 146 | let valueTypeId = decoded.readUInt16BE(1); 147 | let correctValueTypeId = false; 148 | let valueRes: SecIDv2ValueType | undefined; 149 | 150 | if (valueVersion === NUMBER_VERSION) { 151 | correctValueTypeId = true; 152 | valueRes = decoded.readUInt32BE(3); 153 | } else if (valueVersion === STRING_VERSION) { 154 | correctValueTypeId = true; 155 | let stringLen = decoded.readUInt16BE(3); 156 | valueRes = decoded.slice(5, 5 + stringLen).toString('utf-8'); 157 | } 158 | 159 | // Constant time integrity check 160 | let correctVersion = valueVersion === NUMBER_VERSION || valueVersion === STRING_VERSION; 161 | let correctType = false; 162 | if (typeof type === 'number') { 163 | correctType = valueTypeId === type; 164 | } else { 165 | correctType = type.has(valueTypeId); 166 | } 167 | if (correctType && correctVersion && hmacCorrect && correctValueTypeId && valueRes !== undefined) { 168 | return { id: valueRes, type: valueTypeId }; 169 | } 170 | throw new IDMailformedError('Invalid id: ' + valuestr); 171 | } 172 | 173 | export class SecID { 174 | public readonly typeName: string; 175 | public readonly typeId: number; 176 | private readonly valueType: SecIDv2ValueTypeName; 177 | private readonly encryptionKey: Buffer; 178 | private readonly encryptionIv: Buffer; 179 | private readonly hmacKey: Buffer; 180 | private readonly style: SecIDStyle; 181 | private readonly hashids: Hashids; 182 | 183 | constructor( 184 | typeName: string, 185 | typeId: number, 186 | valueType: SecIDv2ValueTypeName, 187 | encryptionKey: Buffer, 188 | encryptionIv: Buffer, 189 | hmacKey: Buffer, 190 | style: SecIDStyle, 191 | hashids: Hashids 192 | ) { 193 | this.typeName = typeName; 194 | this.typeId = typeId; 195 | this.valueType = valueType; 196 | this.encryptionKey = encryptionKey; 197 | this.encryptionIv = encryptionIv; 198 | this.hmacKey = hmacKey; 199 | this.style = style; 200 | this.hashids = hashids; 201 | } 202 | 203 | serialize(value: T) { 204 | let encrypted = encrypt(value, this.valueType, this.typeId, this.encryptionKey, this.encryptionIv, this.hmacKey); 205 | return encodeStyle(encrypted, this.style, this.hashids); 206 | } 207 | 208 | parse(value: string): T { 209 | // Decode style 210 | let source = decodeStyle(value, this.style, this.hashids); 211 | if (source.length < MIN_KEY_LENGTH) { 212 | throw new IDMailformedError('Invalid id'); 213 | } 214 | return decrypt(value, source, this.typeId, this.encryptionKey, this.encryptionIv, this.hmacKey).id as T; 215 | } 216 | } 217 | 218 | export class SecIDFactory { 219 | private readonly typeSalt: string; 220 | private readonly encryptionKey: Buffer; 221 | private readonly encryptionIv: Buffer; 222 | private readonly hmacKey: Buffer; 223 | private readonly style: SecIDStyle; 224 | private readonly hashids: Hashids; 225 | private knownTypes = new Set(); 226 | private knownSecIDS = new Map(); 227 | 228 | constructor(secret: string, style: SecIDStyle = 'hashids') { 229 | this.style = style; 230 | this.typeSalt = Crypto.pbkdf2Sync(secret, typeKeySalt, 100000, 32, 'sha512').toString('hex'); 231 | this.encryptionKey = Crypto.pbkdf2Sync(secret, encryptionKeySalt, 100000, 16, 'sha512'); 232 | this.encryptionIv = Crypto.pbkdf2Sync(secret, encryptionIvSalt, 100000, 16, 'sha512'); 233 | this.hmacKey = Crypto.pbkdf2Sync(secret, hmacSecretSalt, 100000, 64, 'sha512'); 234 | this.hashids = new Hashids(Crypto.pbkdf2Sync(secret, hashidsSalt, 100000, 32, 'sha512').toString('hex')); 235 | } 236 | 237 | resolve(value: string) { 238 | let source = decodeStyle(value, this.style, this.hashids); 239 | if (source.length < MIN_KEY_LENGTH) { 240 | throw new IDMailformedError('Invalid id'); 241 | } 242 | let res = decrypt(value, source, this.knownTypes, this.encryptionKey, this.encryptionIv, this.hmacKey); 243 | return { 244 | id: res.id, 245 | type: this.knownSecIDS.get(res.type)!! 246 | }; 247 | } 248 | 249 | createId(type: string) { 250 | return this.doCreateId(type, 'number'); 251 | } 252 | 253 | createStringId(type: string) { 254 | return this.doCreateId(type, 'string'); 255 | } 256 | 257 | private doCreateId(type: string, valueType: SecIDv2ValueTypeName) { 258 | // Hashing of type name 259 | // We don't need to make this hash secure. 260 | // Just to "compress" and use hash instead of a full name. 261 | 262 | // Using simple hash: sha1 263 | let hash = Crypto.createHash('sha1'); 264 | // Append type salt to avoid duplicates in different factory instances (with different secret). 265 | hash.update(this.typeSalt, 'utf8'); 266 | // Append type as is 267 | hash.update(type.toLowerCase(), 'utf8'); 268 | // Read first two bytes of hash 269 | let res = hash.digest(); 270 | let typeId = res.readUInt16BE(0); 271 | 272 | // Check for uniques since there could be collisions 273 | if (this.knownTypes.has(typeId)) { 274 | throw Error('SecID type collision for "' + type + '", please try to use different name.'); 275 | } 276 | this.knownTypes.add(typeId); 277 | 278 | // Build SecID instance 279 | let id = new SecID(type, typeId, valueType, this.encryptionKey, this.encryptionIv, this.hmacKey, this.style, this.hashids); 280 | this.knownSecIDS.set(typeId, id); 281 | return id; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /packages/secure-id/src/base64.ts: -------------------------------------------------------------------------------- 1 | function unescape(str: string) { 2 | return (str + '==='.slice((str.length + 3) % 4)) 3 | .replace(/-/g, '+') 4 | .replace(/_/g, '/'); 5 | } 6 | 7 | function escape(str: string) { 8 | return str.replace(/\+/g, '-') 9 | .replace(/\//g, '_') 10 | .replace(/=/g, ''); 11 | } 12 | 13 | export function encodeBuffer(buffer: Buffer) { 14 | return escape(buffer.toString('base64')); 15 | } 16 | 17 | export function decodeBuffer(str: string) { 18 | return Buffer.from(unescape(str), 'base64'); 19 | } -------------------------------------------------------------------------------- /packages/secure-id/src/index.ts: -------------------------------------------------------------------------------- 1 | export { SecID, SecIDFactory } from './SecID'; -------------------------------------------------------------------------------- /packages/secure-id/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "rootDir": "src", 6 | "outDir": "lib" 7 | }, 8 | "references": [ 9 | 10 | ] 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["es6", "dom"], 6 | "jsx": "react", 7 | }, 8 | "files": [], 9 | "references": [{ 10 | "path": "./packages/secure-id" 11 | }] 12 | } -------------------------------------------------------------------------------- /tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["es6", "dom"], 6 | "sourceMap": true, 7 | "allowJs": false, 8 | "jsx": "react", 9 | "moduleResolution": "node", 10 | "forceConsistentCasingInFileNames": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": false, 17 | "allowSyntheticDefaultImports": true, 18 | "strictPropertyInitialization": true, 19 | "typeRoots": ["./node_modules/@types"], 20 | "resolveJsonModule": true, 21 | "importHelpers": false, 22 | "composite": true 23 | }, 24 | "exclude": [ 25 | "node_modules/**", 26 | "packages/*/lib/**", 27 | "packages/*/node_modules/**" 28 | ] 29 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint-react", "tslint-config-prettier"], 3 | "rules": { 4 | "ban": false, 5 | "class-name": true, 6 | "comment-format": [true, "check-space"], 7 | "curly": true, 8 | "eofline": false, 9 | "forin": true, 10 | "indent": [true, "spaces"], 11 | "interface-name": [true, "never-prefix"], 12 | "jsdoc-format": true, 13 | "jsx-no-lambda": false, 14 | "jsx-no-multiline-js": false, 15 | "jsx-alignment": true, 16 | "label-position": true, 17 | "member-ordering": [ 18 | true, 19 | "private-before-public", 20 | "static-before-instance" 21 | ], 22 | "no-any": false, 23 | "no-arg": true, 24 | "no-bitwise": false, 25 | "no-consecutive-blank-lines": true, 26 | "no-construct": true, 27 | "no-debugger": true, 28 | "no-duplicate-variable": true, 29 | "no-empty": true, 30 | "no-eval": true, 31 | "no-shadowed-variable": true, 32 | "no-string-literal": true, 33 | "no-switch-case-fall-through": true, 34 | "no-trailing-whitespace": false, 35 | "no-unused-expression": true, 36 | "no-use-before-declare": true, 37 | "one-line": [ 38 | true, 39 | "check-catch", 40 | "check-else", 41 | "check-open-brace", 42 | "check-whitespace" 43 | ], 44 | "radix": true, 45 | "switch-default": true, 46 | "triple-equals": [true, "allow-null-check"], 47 | "typedef": [true, "parameter", "property-declaration"], 48 | "typedef-whitespace": [ 49 | true, 50 | { 51 | "call-signature": "nospace", 52 | "index-signature": "nospace", 53 | "parameter": "nospace", 54 | "property-declaration": "nospace", 55 | "variable-declaration": "nospace" 56 | } 57 | ], 58 | "variable-name": [ 59 | true, 60 | "ban-keywords", 61 | "check-format", 62 | "allow-leading-underscore", 63 | "allow-pascal-case" 64 | ], 65 | "whitespace": [ 66 | true, 67 | "check-branch", 68 | "check-decl", 69 | "check-module", 70 | "check-operator", 71 | "check-separator", 72 | "check-type", 73 | "check-typecast" 74 | ], 75 | "jsx-boolean-value": false 76 | }, 77 | "jsRules": { 78 | "no-empty": true 79 | }, 80 | "linterOptions": { 81 | "exclude": ["**/*.json"] 82 | } 83 | } --------------------------------------------------------------------------------