@tokamakdev/plugin-secure-storage 0.1.0-beta.107 → 0.1.0-beta.112

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.
@@ -18,6 +18,10 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
18
18
  self.host = host
19
19
  }
20
20
 
21
+ func executor(for method: String) -> DispatchQueue {
22
+ ["get", "replace", "set"].contains(method) ? .main : queue
23
+ }
24
+
21
25
  #if os(macOS)
22
26
  /// macOS grants the data protection keychain only to apps whose provisioning
23
27
  /// profile provides an application identifier.
@@ -32,16 +36,19 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
32
36
  method: String,
33
37
  arguments: Any,
34
38
  reply: @escaping TokamakPluginReply
35
- ) {
39
+ ) -> (() -> Void)? {
36
40
  let arguments = arguments as? [String: Any] ?? [:]
37
41
  let prompting =
38
- isUnlocked ? Result { () throws(TokamakPluginError) in try host.requireUI() } : nil
42
+ ["get", "replace", "set"].contains(method) && isUnlocked
43
+ ? Result { () throws(TokamakPluginError) in try host.requireUI() } : nil
44
+ let authentication = LAContext()
39
45
  queue.async {
40
46
  reply(
41
47
  Result { () throws(TokamakPluginError) in
42
- try self.perform(method, arguments, prompting: prompting)
48
+ try self.perform(method, arguments, prompting: prompting, authentication: authentication)
43
49
  })
44
50
  }
51
+ return { authentication.invalidate() }
45
52
  }
46
53
 
47
54
  /// Whether the device's protected data is available, which it is not while
@@ -60,7 +67,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
60
67
  private func perform(
61
68
  _ method: String,
62
69
  _ arguments: [String: Any],
63
- prompting: Result<Void, TokamakPluginError>?
70
+ prompting: Result<Void, TokamakPluginError>?,
71
+ authentication: LAContext
64
72
  ) throws(TokamakPluginError) -> Any? {
65
73
  #if os(macOS)
66
74
  // Unentitled reads report errSecItemNotFound rather than a missing entitlement.
@@ -80,7 +88,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
80
88
  group: group,
81
89
  stored(arguments),
82
90
  prompt: optionalString(arguments, "prompt"),
83
- prompting: prompting
91
+ prompting: prompting,
92
+ authentication: authentication
84
93
  ))
85
94
  return nil
86
95
  case "set":
@@ -91,7 +100,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
91
100
  group: group,
92
101
  stored,
93
102
  prompt: optionalString(arguments, "prompt"),
94
- prompting: prompting
103
+ prompting: prompting,
104
+ authentication: authentication
95
105
  )
96
106
  try check(status == errSecItemNotFound ? add(name, group: group, stored) : status)
97
107
  return nil
@@ -100,7 +110,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
100
110
  requiredString(arguments, "name"),
101
111
  group: group,
102
112
  prompt: optionalString(arguments, "prompt"),
103
- prompting: prompting
113
+ prompting: prompting,
114
+ authentication: authentication
104
115
  )
105
116
  case "delete":
106
117
  let name = try requiredString(arguments, "name")
@@ -141,14 +152,15 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
141
152
  group: String?,
142
153
  _ stored: [CFString: Any],
143
154
  prompt: String?,
144
- prompting: Result<Void, TokamakPluginError>?
155
+ prompting: Result<Void, TokamakPluginError>?,
156
+ authentication: LAContext
145
157
  ) throws(TokamakPluginError) -> OSStatus {
146
158
  var item = query(name, group: group)
147
159
  item[kSecUseAuthenticationContext] = Self.contextWithoutUI()
148
160
  let status = SecItemUpdate(item as CFDictionary, stored as CFDictionary)
149
161
  guard status == errSecInteractionNotAllowed, let prompting else { return status }
150
162
  try prompting.get()
151
- item[kSecUseAuthenticationContext] = Self.context(prompt: prompt)
163
+ item[kSecUseAuthenticationContext] = Self.context(authentication, prompt: prompt)
152
164
  return SecItemUpdate(item as CFDictionary, stored as CFDictionary)
153
165
  }
154
166
 
@@ -158,7 +170,8 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
158
170
  _ name: String,
159
171
  group: String?,
160
172
  prompt: String?,
161
- prompting: Result<Void, TokamakPluginError>?
173
+ prompting: Result<Void, TokamakPluginError>?,
174
+ authentication: LAContext
162
175
  ) throws(TokamakPluginError) -> String {
163
176
  var item = query(name, group: group)
164
177
  item[kSecReturnData] = true
@@ -167,7 +180,7 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
167
180
  var status = SecItemCopyMatching(item as CFDictionary, &data)
168
181
  if status == errSecInteractionNotAllowed, let prompting {
169
182
  try prompting.get()
170
- item[kSecUseAuthenticationContext] = Self.context(prompt: prompt)
183
+ item[kSecUseAuthenticationContext] = Self.context(authentication, prompt: prompt)
171
184
  status = SecItemCopyMatching(item as CFDictionary, &data)
172
185
  // The first read found the value, so it exists but the system can no longer decrypt it.
173
186
  if status == errSecItemNotFound {
@@ -192,11 +205,9 @@ final class TokamakSecureStoragePlugin: TokamakPlugin {
192
205
  return context
193
206
  }
194
207
 
195
- /// A context whose system prompt shows `prompt`, or nil for the default prompt.
196
- private static func context(prompt: String?) -> LAContext? {
197
- guard let prompt, !prompt.isEmpty else { return nil }
198
- let context = LAContext()
199
- context.localizedReason = prompt
208
+ /// The invocation's authentication context, with the requested prompt.
209
+ private static func context(_ context: LAContext, prompt: String?) -> LAContext {
210
+ if let prompt, !prompt.isEmpty { context.localizedReason = prompt }
200
211
  return context
201
212
  }
202
213
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-secure-storage",
3
- "version": "0.1.0-beta.107",
3
+ "version": "0.1.0-beta.112",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -8,10 +8,11 @@
8
8
  },
9
9
  "type": "module",
10
10
  "exports": {
11
- ".": "./src/index.ts"
11
+ ".": "./src/index.ts",
12
+ "./android": "./android/bindings.ts"
12
13
  },
13
14
  "peerDependencies": {
14
- "@tokamakdev/plugin": "0.1.0-beta.107"
15
+ "@tokamakdev/plugin": "0.1.0-beta.112"
15
16
  },
16
17
  "files": [
17
18
  "android",
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Plugin } from "@tokamakdev/plugin";
1
+ import { Plugin, type TokamakContext } from "@tokamakdev/plugin";
2
2
 
3
3
  /** When a stored value can be read. */
4
4
  export type Readable = "whenUnlocked" | "afterFirstUnlock";
@@ -45,8 +45,8 @@ export interface DeleteOptions {
45
45
  }
46
46
 
47
47
  class SecureStorage extends Plugin {
48
- constructor() {
49
- super("secure-storage");
48
+ constructor(ctx: TokamakContext) {
49
+ super(ctx, "secure-storage");
50
50
  }
51
51
 
52
52
  /** Stores a new value; rejects with `DuplicateItemError` when `name` has one. */
@@ -91,4 +91,4 @@ function stored(name: string, value: string, options: CreateOptions) {
91
91
  };
92
92
  }
93
93
 
94
- export const secureStorage = new SecureStorage();
94
+ export function secureStorage(ctx: TokamakContext): SecureStorage { return new SecureStorage(ctx); }
@@ -1,20 +1,61 @@
1
1
  {
2
- "schemaVersion": 1,
2
+ "schemaVersion": 2,
3
3
  "id": "secure-storage",
4
+ "bindings": {
5
+ "android": {
6
+ "sdk": 35,
7
+ "modules": [
8
+ "android.security.keystore",
9
+ "java.security.KeyStore",
10
+ "javax.crypto.Cipher",
11
+ "javax.crypto.KeyGenerator"
12
+ ]
13
+ }
14
+ },
4
15
  "platforms": {
5
16
  "macos": {
6
17
  "class": "TokamakSecureStoragePlugin",
7
- "sources": ["apple/SecureStoragePlugin.swift"]
18
+ "sources": [
19
+ "apple/SecureStoragePlugin.swift"
20
+ ]
8
21
  },
9
22
  "ios": {
10
23
  "class": "TokamakSecureStoragePlugin",
11
- "sources": ["apple/SecureStoragePlugin.swift"],
24
+ "sources": [
25
+ "apple/SecureStoragePlugin.swift"
26
+ ],
12
27
  "plist": "apple/Info.plist"
13
28
  },
14
29
  "android": {
15
30
  "class": "com.tokamak.plugins.securestorage.TokamakSecureStoragePlugin",
16
- "sources": ["android/SecureStoragePlugin.kt"],
31
+ "sources": [
32
+ "android/SecureStoragePlugin.kt"
33
+ ],
17
34
  "manifest": "android/AndroidManifest.xml"
18
35
  }
36
+ },
37
+ "api": {
38
+ "module": "src/index.ts",
39
+ "export": "secureStorage"
40
+ },
41
+ "operations": {
42
+ "get": {
43
+ "foreground": [
44
+ "ios",
45
+ "android"
46
+ ]
47
+ },
48
+ "replace": {
49
+ "foreground": [
50
+ "ios",
51
+ "android"
52
+ ]
53
+ },
54
+ "set": {
55
+ "foreground": [
56
+ "ios",
57
+ "android"
58
+ ]
59
+ }
19
60
  }
20
61
  }