@kokuin/electron 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/lib/entry.d.ts +15 -0
- package/lib/entry.js +71 -0
- package/lib/identity.d.ts +4 -0
- package/lib/identity.js +60 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +13 -0
- package/lib/store.d.ts +8 -0
- package/lib/store.js +39 -0
- package/lib/types.d.ts +4 -0
- package/lib/types.js +1 -0
- package/package.json +46 -0
package/README.md
ADDED
package/lib/entry.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { KeyEntry } from '@kokuin/token';
|
|
2
|
+
import type { KeyStorage } from './types.js';
|
|
3
|
+
export declare class ElectronKeyEntry implements KeyEntry<string> {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(storage: KeyStorage, keyID: string, key?: string);
|
|
6
|
+
get keyID(): string;
|
|
7
|
+
getAsync(): Promise<string | null>;
|
|
8
|
+
get(): string | null;
|
|
9
|
+
setAsync(key: string): Promise<void>;
|
|
10
|
+
set(key: string): void;
|
|
11
|
+
provideAsync(): Promise<string>;
|
|
12
|
+
provide(): string;
|
|
13
|
+
removeAsync(): Promise<void>;
|
|
14
|
+
remove(): void;
|
|
15
|
+
}
|
package/lib/entry.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { randomPrivateKey } from '@kokuin/token';
|
|
2
|
+
import { fromB64, toB64 } from '@sozai/codec';
|
|
3
|
+
import { safeStorage } from 'electron';
|
|
4
|
+
function encryptKey(encoded) {
|
|
5
|
+
return toB64(safeStorage.encryptString(encoded));
|
|
6
|
+
}
|
|
7
|
+
function decryptKey(encrypted) {
|
|
8
|
+
return safeStorage.decryptString(Buffer.from(fromB64(encrypted)));
|
|
9
|
+
}
|
|
10
|
+
// Stored as base64
|
|
11
|
+
export class ElectronKeyEntry {
|
|
12
|
+
#keyID;
|
|
13
|
+
#key;
|
|
14
|
+
#storage;
|
|
15
|
+
constructor(storage, keyID, key){
|
|
16
|
+
this.#keyID = keyID;
|
|
17
|
+
this.#key = key;
|
|
18
|
+
this.#storage = storage;
|
|
19
|
+
}
|
|
20
|
+
get keyID() {
|
|
21
|
+
return this.#keyID;
|
|
22
|
+
}
|
|
23
|
+
getAsync() {
|
|
24
|
+
return Promise.resolve(this.get());
|
|
25
|
+
}
|
|
26
|
+
get() {
|
|
27
|
+
if (this.#key != null) {
|
|
28
|
+
return this.#key;
|
|
29
|
+
}
|
|
30
|
+
const encrypted = this.#storage.getKeys()[this.#keyID];
|
|
31
|
+
if (encrypted == null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const key = decryptKey(encrypted);
|
|
35
|
+
if (key == null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
this.#key = key;
|
|
39
|
+
return this.#key;
|
|
40
|
+
}
|
|
41
|
+
setAsync(key) {
|
|
42
|
+
return Promise.resolve(this.set(key));
|
|
43
|
+
}
|
|
44
|
+
set(key) {
|
|
45
|
+
const encrypted = encryptKey(key);
|
|
46
|
+
this.#storage.setKeys({
|
|
47
|
+
[this.#keyID]: encrypted
|
|
48
|
+
});
|
|
49
|
+
this.#key = key;
|
|
50
|
+
}
|
|
51
|
+
provideAsync() {
|
|
52
|
+
return Promise.resolve(this.provide());
|
|
53
|
+
}
|
|
54
|
+
provide() {
|
|
55
|
+
const existing = this.get();
|
|
56
|
+
if (existing != null) {
|
|
57
|
+
return existing;
|
|
58
|
+
}
|
|
59
|
+
const privateKey = toB64(randomPrivateKey());
|
|
60
|
+
this.set(privateKey);
|
|
61
|
+
return privateKey;
|
|
62
|
+
}
|
|
63
|
+
removeAsync() {
|
|
64
|
+
return Promise.resolve(this.remove());
|
|
65
|
+
}
|
|
66
|
+
remove() {
|
|
67
|
+
const { [this.#keyID]: _, ...keys } = this.#storage.getKeys();
|
|
68
|
+
this.#storage.setKeys(keys);
|
|
69
|
+
this.#key = undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type FullIdentity } from '@kokuin/token';
|
|
2
|
+
import { ElectronKeyStore } from './store.js';
|
|
3
|
+
export declare function provideFullIdentity(store: ElectronKeyStore | string, keyID: string): FullIdentity;
|
|
4
|
+
export declare function provideFullIdentityAsync(store: ElectronKeyStore | string, keyID: string): Promise<FullIdentity>;
|
package/lib/identity.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createFullIdentity, decodePrivateKey } from '@kokuin/token';
|
|
2
|
+
import { getLogger } from '@sozai/log';
|
|
3
|
+
import { AttributeKeys, createTracer, SpanNames, withSpan, withSyncSpan } from '@sozai/otel';
|
|
4
|
+
import { ElectronKeyStore } from './store.js';
|
|
5
|
+
const tracer = createTracer('keystore.electron');
|
|
6
|
+
const logger = getLogger([
|
|
7
|
+
'kokuin',
|
|
8
|
+
'electron'
|
|
9
|
+
]);
|
|
10
|
+
function getStore(store) {
|
|
11
|
+
return typeof store === 'string' ? ElectronKeyStore.open(store) : store;
|
|
12
|
+
}
|
|
13
|
+
export function provideFullIdentity(store, keyID) {
|
|
14
|
+
return withSyncSpan(tracer, SpanNames.KEYSTORE_GET_OR_CREATE, {
|
|
15
|
+
attributes: {
|
|
16
|
+
[AttributeKeys.KEYSTORE_STORE_TYPE]: 'electron'
|
|
17
|
+
}
|
|
18
|
+
}, (span)=>{
|
|
19
|
+
const entry = getStore(store).entry(keyID);
|
|
20
|
+
const existing = entry.get();
|
|
21
|
+
if (existing != null) {
|
|
22
|
+
const identity = createFullIdentity(decodePrivateKey(existing));
|
|
23
|
+
span.setAttribute(AttributeKeys.AUTH_DID, identity.id);
|
|
24
|
+
span.setAttribute(AttributeKeys.KEYSTORE_KEY_CREATED, false);
|
|
25
|
+
return identity;
|
|
26
|
+
}
|
|
27
|
+
const key = entry.provide();
|
|
28
|
+
const identity = createFullIdentity(decodePrivateKey(key));
|
|
29
|
+
span.setAttribute(AttributeKeys.AUTH_DID, identity.id);
|
|
30
|
+
span.setAttribute(AttributeKeys.KEYSTORE_KEY_CREATED, true);
|
|
31
|
+
logger.info('New identity generated: {did}', {
|
|
32
|
+
did: identity.id
|
|
33
|
+
});
|
|
34
|
+
return identity;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export async function provideFullIdentityAsync(store, keyID) {
|
|
38
|
+
return withSpan(tracer, SpanNames.KEYSTORE_GET_OR_CREATE, {
|
|
39
|
+
attributes: {
|
|
40
|
+
[AttributeKeys.KEYSTORE_STORE_TYPE]: 'electron'
|
|
41
|
+
}
|
|
42
|
+
}, async (span)=>{
|
|
43
|
+
const entry = getStore(store).entry(keyID);
|
|
44
|
+
const existing = await entry.getAsync();
|
|
45
|
+
if (existing != null) {
|
|
46
|
+
const identity = createFullIdentity(decodePrivateKey(existing));
|
|
47
|
+
span.setAttribute(AttributeKeys.AUTH_DID, identity.id);
|
|
48
|
+
span.setAttribute(AttributeKeys.KEYSTORE_KEY_CREATED, false);
|
|
49
|
+
return identity;
|
|
50
|
+
}
|
|
51
|
+
const key = await entry.provideAsync();
|
|
52
|
+
const identity = createFullIdentity(decodePrivateKey(key));
|
|
53
|
+
span.setAttribute(AttributeKeys.AUTH_DID, identity.id);
|
|
54
|
+
span.setAttribute(AttributeKeys.KEYSTORE_KEY_CREATED, true);
|
|
55
|
+
logger.info('New identity generated: {did}', {
|
|
56
|
+
did: identity.id
|
|
57
|
+
});
|
|
58
|
+
return identity;
|
|
59
|
+
});
|
|
60
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron keystore.
|
|
3
|
+
*
|
|
4
|
+
* ## Installation
|
|
5
|
+
*
|
|
6
|
+
* ```sh
|
|
7
|
+
* npm install @kokuin/electron
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* @module electron-keystore
|
|
11
|
+
*/
|
|
12
|
+
export { ElectronKeyEntry } from './entry.js';
|
|
13
|
+
export { provideFullIdentity, provideFullIdentityAsync } from './identity.js';
|
|
14
|
+
export { ElectronKeyStore } from './store.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron keystore.
|
|
3
|
+
*
|
|
4
|
+
* ## Installation
|
|
5
|
+
*
|
|
6
|
+
* ```sh
|
|
7
|
+
* npm install @kokuin/electron
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* @module electron-keystore
|
|
11
|
+
*/ export { ElectronKeyEntry } from './entry.js';
|
|
12
|
+
export { provideFullIdentity, provideFullIdentityAsync } from './identity.js';
|
|
13
|
+
export { ElectronKeyStore } from './store.js';
|
package/lib/store.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { KeyStore } from '@kokuin/token';
|
|
2
|
+
import { ElectronKeyEntry } from './entry.js';
|
|
3
|
+
export declare class ElectronKeyStore implements KeyStore<string, ElectronKeyEntry> {
|
|
4
|
+
#private;
|
|
5
|
+
static open(name?: string): ElectronKeyStore;
|
|
6
|
+
constructor(name: string);
|
|
7
|
+
entry(keyID: string): ElectronKeyEntry;
|
|
8
|
+
}
|
package/lib/store.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Store from 'electron-store';
|
|
2
|
+
import { ElectronKeyEntry } from './entry.js';
|
|
3
|
+
export class ElectronKeyStore {
|
|
4
|
+
static #byName = {};
|
|
5
|
+
static open(name = 'keystore') {
|
|
6
|
+
if (ElectronKeyStore.#byName[name] == null) {
|
|
7
|
+
ElectronKeyStore.#byName[name] = new ElectronKeyStore(name);
|
|
8
|
+
}
|
|
9
|
+
return ElectronKeyStore.#byName[name];
|
|
10
|
+
}
|
|
11
|
+
#entries = {};
|
|
12
|
+
#storage;
|
|
13
|
+
constructor(name){
|
|
14
|
+
const store = new Store({
|
|
15
|
+
name,
|
|
16
|
+
schema: {
|
|
17
|
+
keys: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
key: {
|
|
21
|
+
type: 'string'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
defaults: {
|
|
27
|
+
keys: {}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
this.#storage = {
|
|
31
|
+
getKeys: ()=>store.get('keys', {}),
|
|
32
|
+
setKeys: (keys)=>store.set('keys', keys)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
entry(keyID) {
|
|
36
|
+
this.#entries[keyID] ??= new ElectronKeyEntry(this.#storage, keyID);
|
|
37
|
+
return this.#entries[keyID];
|
|
38
|
+
}
|
|
39
|
+
}
|
package/lib/types.d.ts
ADDED
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kokuin/electron",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Electron keystore",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"cryptography",
|
|
8
|
+
"key-management",
|
|
9
|
+
"electron",
|
|
10
|
+
"storage"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/TairuFramework/kokuin",
|
|
15
|
+
"directory": "packages/electron"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "lib/index.js",
|
|
19
|
+
"types": "lib/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./lib/index.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib/*"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@sozai/codec": "^0.1.0",
|
|
29
|
+
"@sozai/log": "^0.1.0",
|
|
30
|
+
"@sozai/otel": "^0.1.0",
|
|
31
|
+
"electron-store": "^11.0.2",
|
|
32
|
+
"@kokuin/token": "^0.1.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"electron": "^42.4.1"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build:clean": "del lib",
|
|
39
|
+
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
40
|
+
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
41
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
42
|
+
"test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
|
|
43
|
+
"test:unit": "vitest run",
|
|
44
|
+
"test": "pnpm run test:types && pnpm run test:unit"
|
|
45
|
+
}
|
|
46
|
+
}
|