capacitor-oidc 0.0.1-alpha.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/CapacitorOidc.podspec +17 -0
- package/LICENSE +201 -0
- package/Package.swift +28 -0
- package/README.md +122 -0
- package/android/build.gradle +75 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/jojopirker/capacitor/oidc/CallbackUriMatcher.java +22 -0
- package/android/src/main/java/com/jojopirker/capacitor/oidc/CapacitorOidcPlugin.java +240 -0
- package/android/src/main/java/com/jojopirker/capacitor/oidc/NativeContract.java +35 -0
- package/android/src/main/java/com/jojopirker/capacitor/oidc/StoredSessionV1.java +111 -0
- package/android/src/main/java/com/jojopirker/capacitor/oidc/TokenVault.java +209 -0
- package/android/src/main/java/com/jojopirker/capacitor/oidc/VaultEntryCodec.java +56 -0
- package/dist/esm/capacitor-navigator.d.ts +14 -0
- package/dist/esm/capacitor-navigator.js +68 -0
- package/dist/esm/capacitor-navigator.js.map +1 -0
- package/dist/esm/capacitor-secure-state-store.d.ts +9 -0
- package/dist/esm/capacitor-secure-state-store.js +36 -0
- package/dist/esm/capacitor-secure-state-store.js.map +1 -0
- package/dist/esm/capacitor-user-manager.d.ts +28 -0
- package/dist/esm/capacitor-user-manager.js +160 -0
- package/dist/esm/capacitor-user-manager.js.map +1 -0
- package/dist/esm/definitions.d.ts +62 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/errors.d.ts +6 -0
- package/dist/esm/errors.js +13 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/generated/native-contract.d.ts +37 -0
- package/dist/esm/generated/native-contract.js +38 -0
- package/dist/esm/generated/native-contract.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/native.d.ts +2 -0
- package/dist/esm/native.js +4 -0
- package/dist/esm/native.js.map +1 -0
- package/dist/plugin.cjs +304 -0
- package/dist/plugin.cjs.map +1 -0
- package/ios/Sources/CapacitorOidcPlugin/CapacitorOidcPlugin.swift +177 -0
- package/ios/Sources/CapacitorOidcPlugin/NativeContract.generated.swift +39 -0
- package/ios/Sources/CapacitorOidcPlugin/StoredSessionV1.swift +35 -0
- package/ios/Sources/CapacitorOidcPlugin/TokenVault.swift +132 -0
- package/ios/Tests/CapacitorOidcPluginTests/StoredSessionV1Tests.swift +38 -0
- package/package.json +85 -0
package/dist/plugin.cjs
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var app = require('@capacitor/app');
|
|
4
|
+
var oidcClientTs = require('oidc-client-ts');
|
|
5
|
+
var core = require('@capacitor/core');
|
|
6
|
+
|
|
7
|
+
// Generated by scripts/generate-native-contract.mjs. Do not edit.
|
|
8
|
+
const nativeContract = {
|
|
9
|
+
pluginName: 'CapacitorOidc',
|
|
10
|
+
fields: {
|
|
11
|
+
url: 'url',
|
|
12
|
+
callbackUrl: 'callbackUrl',
|
|
13
|
+
prefersEphemeralWebBrowserSession: 'prefersEphemeralWebBrowserSession',
|
|
14
|
+
keychainAccessGroup: 'keychainAccessGroup',
|
|
15
|
+
keychainAccessibility: 'keychainAccessibility',
|
|
16
|
+
namespace: 'namespace',
|
|
17
|
+
key: 'key',
|
|
18
|
+
value: 'value',
|
|
19
|
+
keys: 'keys',
|
|
20
|
+
},
|
|
21
|
+
errorCodes: {
|
|
22
|
+
browserUnavailable: 'BROWSER_UNAVAILABLE',
|
|
23
|
+
invalidCallback: 'INVALID_CALLBACK',
|
|
24
|
+
unsupportedRuntime: 'UNSUPPORTED_RUNTIME',
|
|
25
|
+
}};
|
|
26
|
+
|
|
27
|
+
class CapacitorOidcError extends Error {
|
|
28
|
+
code;
|
|
29
|
+
constructor(code, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.name = 'CapacitorOidcError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function unsupported(feature) {
|
|
36
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, `${feature} is not available in a native Capacitor runtime`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const NativeOidc = core.registerPlugin(nativeContract.pluginName);
|
|
40
|
+
|
|
41
|
+
class CapacitorNavigator {
|
|
42
|
+
prefersEphemeralWebBrowserSession;
|
|
43
|
+
constructor(prefersEphemeralWebBrowserSession) {
|
|
44
|
+
this.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession;
|
|
45
|
+
}
|
|
46
|
+
async prepare() {
|
|
47
|
+
return new CapacitorWindow(this.prefersEphemeralWebBrowserSession);
|
|
48
|
+
}
|
|
49
|
+
async callback() {
|
|
50
|
+
unsupported('Navigator callbacks');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class CapacitorWindow {
|
|
54
|
+
prefersEphemeralWebBrowserSession;
|
|
55
|
+
constructor(prefersEphemeralWebBrowserSession) {
|
|
56
|
+
this.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession;
|
|
57
|
+
}
|
|
58
|
+
async navigate({ url }) {
|
|
59
|
+
assertSecureRequestUrl(url);
|
|
60
|
+
const callbackUrl = callbackUrlFromRequest(url);
|
|
61
|
+
const response = await NativeOidc.open({
|
|
62
|
+
[nativeContract.fields.url]: url,
|
|
63
|
+
[nativeContract.fields.callbackUrl]: callbackUrl,
|
|
64
|
+
[nativeContract.fields.prefersEphemeralWebBrowserSession]: this.prefersEphemeralWebBrowserSession,
|
|
65
|
+
});
|
|
66
|
+
if (!isExpectedCallback(response[nativeContract.fields.url], callbackUrl)) {
|
|
67
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.invalidCallback, 'The authentication callback does not match the configured redirect URI');
|
|
68
|
+
}
|
|
69
|
+
return { url: response[nativeContract.fields.url] };
|
|
70
|
+
}
|
|
71
|
+
close() {
|
|
72
|
+
void NativeOidc.cancel();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function assertSecureRequestUrl(requestUrl) {
|
|
76
|
+
const url = new URL(requestUrl);
|
|
77
|
+
const isLocalDevelopment = url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]';
|
|
78
|
+
if (url.protocol !== 'https:' && !isLocalDevelopment) {
|
|
79
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.browserUnavailable, 'OIDC authorization and logout endpoints must use HTTPS');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function callbackUrlFromRequest(requestUrl) {
|
|
83
|
+
const url = new URL(requestUrl);
|
|
84
|
+
const callback = url.searchParams.get('redirect_uri') ?? url.searchParams.get('post_logout_redirect_uri');
|
|
85
|
+
if (!callback) {
|
|
86
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.invalidCallback, 'The OIDC request does not contain a redirect URI');
|
|
87
|
+
}
|
|
88
|
+
return callback;
|
|
89
|
+
}
|
|
90
|
+
function isExpectedCallback(actualValue, expectedValue) {
|
|
91
|
+
const actual = new URL(actualValue);
|
|
92
|
+
const expected = new URL(expectedValue);
|
|
93
|
+
return (actual.protocol.toLowerCase() === expected.protocol.toLowerCase() &&
|
|
94
|
+
actual.host.toLowerCase() === expected.host.toLowerCase() &&
|
|
95
|
+
actual.pathname === expected.pathname);
|
|
96
|
+
}
|
|
97
|
+
class UnsupportedIframeNavigator {
|
|
98
|
+
async prepare() {
|
|
99
|
+
unsupported('Iframe navigation');
|
|
100
|
+
}
|
|
101
|
+
async callback() {
|
|
102
|
+
unsupported('Iframe navigation');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class CapacitorSecureStateStore {
|
|
107
|
+
namespace;
|
|
108
|
+
constructor(namespace) {
|
|
109
|
+
this.namespace = namespace;
|
|
110
|
+
}
|
|
111
|
+
async set(key, value) {
|
|
112
|
+
await NativeOidc.storageSet({
|
|
113
|
+
[nativeContract.fields.namespace]: this.namespace,
|
|
114
|
+
[nativeContract.fields.key]: key,
|
|
115
|
+
[nativeContract.fields.value]: value,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async get(key) {
|
|
119
|
+
const result = await NativeOidc.storageGet({
|
|
120
|
+
[nativeContract.fields.namespace]: this.namespace,
|
|
121
|
+
[nativeContract.fields.key]: key,
|
|
122
|
+
});
|
|
123
|
+
return result[nativeContract.fields.value];
|
|
124
|
+
}
|
|
125
|
+
async remove(key) {
|
|
126
|
+
const result = await NativeOidc.storageRemove({
|
|
127
|
+
[nativeContract.fields.namespace]: this.namespace,
|
|
128
|
+
[nativeContract.fields.key]: key,
|
|
129
|
+
});
|
|
130
|
+
return result[nativeContract.fields.value];
|
|
131
|
+
}
|
|
132
|
+
async getAllKeys() {
|
|
133
|
+
const result = await NativeOidc.storageGetAllKeys({
|
|
134
|
+
[nativeContract.fields.namespace]: this.namespace,
|
|
135
|
+
});
|
|
136
|
+
return result[nativeContract.fields.keys];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class CapacitorUserManager extends oidcClientTs.UserManager {
|
|
141
|
+
storageNamespace;
|
|
142
|
+
refreshPromise;
|
|
143
|
+
appStateListener;
|
|
144
|
+
constructor(settings, nativeOptions, storageNamespace) {
|
|
145
|
+
const stateStore = new CapacitorSecureStateStore(`${storageNamespace}.transactions`);
|
|
146
|
+
const userStore = new CapacitorSecureStateStore(`${storageNamespace}.session`);
|
|
147
|
+
const navigator = new CapacitorNavigator(nativeOptions.ios?.prefersEphemeralWebBrowserSession ?? false);
|
|
148
|
+
super({
|
|
149
|
+
...settings,
|
|
150
|
+
response_type: 'code',
|
|
151
|
+
disablePKCE: false,
|
|
152
|
+
monitorSession: false,
|
|
153
|
+
popup_redirect_uri: settings.redirect_uri,
|
|
154
|
+
popup_post_logout_redirect_uri: settings.post_logout_redirect_uri,
|
|
155
|
+
stateStore,
|
|
156
|
+
userStore,
|
|
157
|
+
}, navigator, navigator, new UnsupportedIframeNavigator());
|
|
158
|
+
this.storageNamespace = storageNamespace;
|
|
159
|
+
}
|
|
160
|
+
static async create(settings, nativeOptions = {}) {
|
|
161
|
+
assertRuntime();
|
|
162
|
+
assertSettings(settings);
|
|
163
|
+
await NativeOidc.configure({
|
|
164
|
+
[nativeContract.fields.prefersEphemeralWebBrowserSession]: nativeOptions.ios?.prefersEphemeralWebBrowserSession,
|
|
165
|
+
[nativeContract.fields.keychainAccessGroup]: nativeOptions.ios?.keychainAccessGroup,
|
|
166
|
+
[nativeContract.fields.keychainAccessibility]: nativeOptions.ios?.keychainAccessibility,
|
|
167
|
+
});
|
|
168
|
+
const manager = new CapacitorUserManager(settings, nativeOptions, nativeOptions.storageNamespace ?? 'default');
|
|
169
|
+
await manager.getValidUser();
|
|
170
|
+
manager.appStateListener = await app.App.addListener('appStateChange', ({ isActive }) => {
|
|
171
|
+
if (isActive) {
|
|
172
|
+
void manager
|
|
173
|
+
.getValidUser()
|
|
174
|
+
.catch((error) => manager.events._raiseSilentRenewError(error instanceof Error ? error : new Error('Silent renewal failed')));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return manager;
|
|
178
|
+
}
|
|
179
|
+
async signin(args = {}) {
|
|
180
|
+
return super.signinPopup(args);
|
|
181
|
+
}
|
|
182
|
+
async signout(args = {}) {
|
|
183
|
+
await super.signoutPopup(args);
|
|
184
|
+
}
|
|
185
|
+
async getValidUser(minimumValiditySeconds = 60) {
|
|
186
|
+
const user = await this.getUser();
|
|
187
|
+
if (!user)
|
|
188
|
+
return null;
|
|
189
|
+
if (user.expires_in === undefined || user.expires_in > minimumValiditySeconds)
|
|
190
|
+
return user;
|
|
191
|
+
return this.signinSilent();
|
|
192
|
+
}
|
|
193
|
+
signinSilent(args = {}) {
|
|
194
|
+
if (!this.refreshPromise) {
|
|
195
|
+
this.refreshPromise = this.refresh(args).finally(() => {
|
|
196
|
+
this.refreshPromise = undefined;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return this.refreshPromise;
|
|
200
|
+
}
|
|
201
|
+
async refresh(args) {
|
|
202
|
+
const user = await this.getUser();
|
|
203
|
+
if (!user?.refresh_token) {
|
|
204
|
+
if (user?.expired)
|
|
205
|
+
await this.removeUser();
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const refresh = super.signinSilent({ ...args, forceIframeAuth: false }).catch(async (error) => {
|
|
209
|
+
if (error instanceof oidcClientTs.ErrorResponse && error.error === 'invalid_grant')
|
|
210
|
+
await this.removeUser();
|
|
211
|
+
throw error;
|
|
212
|
+
});
|
|
213
|
+
return refresh;
|
|
214
|
+
}
|
|
215
|
+
async storeUser(user) {
|
|
216
|
+
await super.storeUser(user);
|
|
217
|
+
await NativeOidc.setSessionSnapshot({
|
|
218
|
+
[nativeContract.fields.namespace]: this.storageNamespace,
|
|
219
|
+
[nativeContract.fields.value]: user
|
|
220
|
+
? JSON.stringify(toStoredSession(user, this.settings.authority, this.settings.client_id))
|
|
221
|
+
: null,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async cancel() {
|
|
225
|
+
await NativeOidc.cancel();
|
|
226
|
+
}
|
|
227
|
+
async dispose() {
|
|
228
|
+
this.stopSilentRenew();
|
|
229
|
+
await this.appStateListener?.remove();
|
|
230
|
+
await this.cancel();
|
|
231
|
+
}
|
|
232
|
+
async signinRedirect() {
|
|
233
|
+
unsupported('Redirect navigation');
|
|
234
|
+
}
|
|
235
|
+
async signinRedirectCallback(_url) {
|
|
236
|
+
unsupported('Redirect navigation');
|
|
237
|
+
}
|
|
238
|
+
async signinCallback(_url) {
|
|
239
|
+
unsupported('Browser callback dispatch');
|
|
240
|
+
}
|
|
241
|
+
async signinResourceOwnerCredentials(_args) {
|
|
242
|
+
unsupported('Resource Owner Password Credentials');
|
|
243
|
+
}
|
|
244
|
+
async signinSilentCallback(_url) {
|
|
245
|
+
unsupported('Iframe navigation');
|
|
246
|
+
}
|
|
247
|
+
async signoutRedirect() {
|
|
248
|
+
unsupported('Redirect navigation');
|
|
249
|
+
}
|
|
250
|
+
async signoutRedirectCallback(_url) {
|
|
251
|
+
unsupported('Redirect navigation');
|
|
252
|
+
}
|
|
253
|
+
async signoutCallback(_url, _keepOpen) {
|
|
254
|
+
unsupported('Browser callback dispatch');
|
|
255
|
+
}
|
|
256
|
+
async signoutSilent() {
|
|
257
|
+
unsupported('Iframe logout');
|
|
258
|
+
}
|
|
259
|
+
async signoutSilentCallback(_url) {
|
|
260
|
+
unsupported('Iframe logout');
|
|
261
|
+
}
|
|
262
|
+
async querySessionStatus() {
|
|
263
|
+
unsupported('Browser session monitoring');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function assertRuntime() {
|
|
267
|
+
if (!globalThis.crypto?.subtle || !globalThis.crypto.getRandomValues) {
|
|
268
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, 'Web Crypto is required by oidc-client-ts');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function assertSettings(settings) {
|
|
272
|
+
if ('client_secret' in settings) {
|
|
273
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, 'Native public clients must not contain a client secret');
|
|
274
|
+
}
|
|
275
|
+
if ('client_authentication' in settings || 'dpop' in settings) {
|
|
276
|
+
throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, 'The supplied settings contain an unsupported native-client option');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function toStoredSession(user, issuer, clientId) {
|
|
280
|
+
return {
|
|
281
|
+
version: 1,
|
|
282
|
+
issuer,
|
|
283
|
+
clientId,
|
|
284
|
+
accessToken: user.access_token,
|
|
285
|
+
refreshToken: user.refresh_token,
|
|
286
|
+
idToken: user.id_token,
|
|
287
|
+
tokenType: user.token_type,
|
|
288
|
+
scope: user.scope,
|
|
289
|
+
expiresAt: user.expires_at,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
Object.defineProperty(exports, "User", {
|
|
294
|
+
enumerable: true,
|
|
295
|
+
get: function () { return oidcClientTs.User; }
|
|
296
|
+
});
|
|
297
|
+
Object.defineProperty(exports, "UserManagerEvents", {
|
|
298
|
+
enumerable: true,
|
|
299
|
+
get: function () { return oidcClientTs.UserManagerEvents; }
|
|
300
|
+
});
|
|
301
|
+
exports.CapacitorOidcError = CapacitorOidcError;
|
|
302
|
+
exports.CapacitorSecureStateStore = CapacitorSecureStateStore;
|
|
303
|
+
exports.CapacitorUserManager = CapacitorUserManager;
|
|
304
|
+
//# sourceMappingURL=plugin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs","sources":["esm/generated/native-contract.js","esm/errors.js","esm/native.js","esm/capacitor-navigator.js","esm/capacitor-secure-state-store.js","esm/capacitor-user-manager.js"],"sourcesContent":["// Generated by scripts/generate-native-contract.mjs. Do not edit.\nexport const nativeContract = {\n pluginName: 'CapacitorOidc',\n methods: {\n configure: 'configure',\n open: 'open',\n cancel: 'cancel',\n storageSet: 'storageSet',\n storageGet: 'storageGet',\n storageRemove: 'storageRemove',\n storageGetAllKeys: 'storageGetAllKeys',\n setSessionSnapshot: 'setSessionSnapshot',\n },\n fields: {\n url: 'url',\n callbackUrl: 'callbackUrl',\n prefersEphemeralWebBrowserSession: 'prefersEphemeralWebBrowserSession',\n keychainAccessGroup: 'keychainAccessGroup',\n keychainAccessibility: 'keychainAccessibility',\n namespace: 'namespace',\n key: 'key',\n value: 'value',\n keys: 'keys',\n },\n errorCodes: {\n authSessionInProgress: 'AUTH_SESSION_IN_PROGRESS',\n userCancelled: 'USER_CANCELLED',\n browserUnavailable: 'BROWSER_UNAVAILABLE',\n invalidCallback: 'INVALID_CALLBACK',\n secureStorageError: 'SECURE_STORAGE_ERROR',\n unsupportedRuntime: 'UNSUPPORTED_RUNTIME',\n },\n keychainAccessibility: {\n afterFirstUnlockThisDeviceOnly: 'afterFirstUnlockThisDeviceOnly',\n whenUnlockedThisDeviceOnly: 'whenUnlockedThisDeviceOnly',\n },\n};\n//# sourceMappingURL=native-contract.js.map","import { nativeContract } from './generated/native-contract';\nexport class CapacitorOidcError extends Error {\n code;\n constructor(code, message) {\n super(message);\n this.code = code;\n this.name = 'CapacitorOidcError';\n }\n}\nexport function unsupported(feature) {\n throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, `${feature} is not available in a native Capacitor runtime`);\n}\n//# sourceMappingURL=errors.js.map","import { registerPlugin } from '@capacitor/core';\nimport { nativeContract } from './generated/native-contract';\nexport const NativeOidc = registerPlugin(nativeContract.pluginName);\n//# sourceMappingURL=native.js.map","import { CapacitorOidcError, unsupported } from './errors';\nimport { nativeContract } from './generated/native-contract';\nimport { NativeOidc } from './native';\nexport class CapacitorNavigator {\n prefersEphemeralWebBrowserSession;\n constructor(prefersEphemeralWebBrowserSession) {\n this.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession;\n }\n async prepare() {\n return new CapacitorWindow(this.prefersEphemeralWebBrowserSession);\n }\n async callback() {\n unsupported('Navigator callbacks');\n }\n}\nclass CapacitorWindow {\n prefersEphemeralWebBrowserSession;\n constructor(prefersEphemeralWebBrowserSession) {\n this.prefersEphemeralWebBrowserSession = prefersEphemeralWebBrowserSession;\n }\n async navigate({ url }) {\n assertSecureRequestUrl(url);\n const callbackUrl = callbackUrlFromRequest(url);\n const response = await NativeOidc.open({\n [nativeContract.fields.url]: url,\n [nativeContract.fields.callbackUrl]: callbackUrl,\n [nativeContract.fields.prefersEphemeralWebBrowserSession]: this.prefersEphemeralWebBrowserSession,\n });\n if (!isExpectedCallback(response[nativeContract.fields.url], callbackUrl)) {\n throw new CapacitorOidcError(nativeContract.errorCodes.invalidCallback, 'The authentication callback does not match the configured redirect URI');\n }\n return { url: response[nativeContract.fields.url] };\n }\n close() {\n void NativeOidc.cancel();\n }\n}\nexport function assertSecureRequestUrl(requestUrl) {\n const url = new URL(requestUrl);\n const isLocalDevelopment = url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]';\n if (url.protocol !== 'https:' && !isLocalDevelopment) {\n throw new CapacitorOidcError(nativeContract.errorCodes.browserUnavailable, 'OIDC authorization and logout endpoints must use HTTPS');\n }\n}\nexport function callbackUrlFromRequest(requestUrl) {\n const url = new URL(requestUrl);\n const callback = url.searchParams.get('redirect_uri') ?? url.searchParams.get('post_logout_redirect_uri');\n if (!callback) {\n throw new CapacitorOidcError(nativeContract.errorCodes.invalidCallback, 'The OIDC request does not contain a redirect URI');\n }\n return callback;\n}\nexport function isExpectedCallback(actualValue, expectedValue) {\n const actual = new URL(actualValue);\n const expected = new URL(expectedValue);\n return (actual.protocol.toLowerCase() === expected.protocol.toLowerCase() &&\n actual.host.toLowerCase() === expected.host.toLowerCase() &&\n actual.pathname === expected.pathname);\n}\nexport class UnsupportedIframeNavigator {\n async prepare() {\n unsupported('Iframe navigation');\n }\n async callback() {\n unsupported('Iframe navigation');\n }\n}\n//# sourceMappingURL=capacitor-navigator.js.map","import { nativeContract } from './generated/native-contract';\nimport { NativeOidc } from './native';\nexport class CapacitorSecureStateStore {\n namespace;\n constructor(namespace) {\n this.namespace = namespace;\n }\n async set(key, value) {\n await NativeOidc.storageSet({\n [nativeContract.fields.namespace]: this.namespace,\n [nativeContract.fields.key]: key,\n [nativeContract.fields.value]: value,\n });\n }\n async get(key) {\n const result = await NativeOidc.storageGet({\n [nativeContract.fields.namespace]: this.namespace,\n [nativeContract.fields.key]: key,\n });\n return result[nativeContract.fields.value];\n }\n async remove(key) {\n const result = await NativeOidc.storageRemove({\n [nativeContract.fields.namespace]: this.namespace,\n [nativeContract.fields.key]: key,\n });\n return result[nativeContract.fields.value];\n }\n async getAllKeys() {\n const result = await NativeOidc.storageGetAllKeys({\n [nativeContract.fields.namespace]: this.namespace,\n });\n return result[nativeContract.fields.keys];\n }\n}\n//# sourceMappingURL=capacitor-secure-state-store.js.map","import { App } from '@capacitor/app';\nimport { ErrorResponse, UserManager, } from 'oidc-client-ts';\nimport { CapacitorNavigator, UnsupportedIframeNavigator } from './capacitor-navigator';\nimport { CapacitorSecureStateStore } from './capacitor-secure-state-store';\nimport { CapacitorOidcError, unsupported } from './errors';\nimport { nativeContract } from './generated/native-contract';\nimport { NativeOidc } from './native';\nexport class CapacitorUserManager extends UserManager {\n storageNamespace;\n refreshPromise;\n appStateListener;\n constructor(settings, nativeOptions, storageNamespace) {\n const stateStore = new CapacitorSecureStateStore(`${storageNamespace}.transactions`);\n const userStore = new CapacitorSecureStateStore(`${storageNamespace}.session`);\n const navigator = new CapacitorNavigator(nativeOptions.ios?.prefersEphemeralWebBrowserSession ?? false);\n super({\n ...settings,\n response_type: 'code',\n disablePKCE: false,\n monitorSession: false,\n popup_redirect_uri: settings.redirect_uri,\n popup_post_logout_redirect_uri: settings.post_logout_redirect_uri,\n stateStore,\n userStore,\n }, navigator, navigator, new UnsupportedIframeNavigator());\n this.storageNamespace = storageNamespace;\n }\n static async create(settings, nativeOptions = {}) {\n assertRuntime();\n assertSettings(settings);\n await NativeOidc.configure({\n [nativeContract.fields.prefersEphemeralWebBrowserSession]: nativeOptions.ios?.prefersEphemeralWebBrowserSession,\n [nativeContract.fields.keychainAccessGroup]: nativeOptions.ios?.keychainAccessGroup,\n [nativeContract.fields.keychainAccessibility]: nativeOptions.ios?.keychainAccessibility,\n });\n const manager = new CapacitorUserManager(settings, nativeOptions, nativeOptions.storageNamespace ?? 'default');\n await manager.getValidUser();\n manager.appStateListener = await App.addListener('appStateChange', ({ isActive }) => {\n if (isActive) {\n void manager\n .getValidUser()\n .catch((error) => manager.events._raiseSilentRenewError(error instanceof Error ? error : new Error('Silent renewal failed')));\n }\n });\n return manager;\n }\n async signin(args = {}) {\n return super.signinPopup(args);\n }\n async signout(args = {}) {\n await super.signoutPopup(args);\n }\n async getValidUser(minimumValiditySeconds = 60) {\n const user = await this.getUser();\n if (!user)\n return null;\n if (user.expires_in === undefined || user.expires_in > minimumValiditySeconds)\n return user;\n return this.signinSilent();\n }\n signinSilent(args = {}) {\n if (!this.refreshPromise) {\n this.refreshPromise = this.refresh(args).finally(() => {\n this.refreshPromise = undefined;\n });\n }\n return this.refreshPromise;\n }\n async refresh(args) {\n const user = await this.getUser();\n if (!user?.refresh_token) {\n if (user?.expired)\n await this.removeUser();\n return null;\n }\n const refresh = super.signinSilent({ ...args, forceIframeAuth: false }).catch(async (error) => {\n if (error instanceof ErrorResponse && error.error === 'invalid_grant')\n await this.removeUser();\n throw error;\n });\n return refresh;\n }\n async storeUser(user) {\n await super.storeUser(user);\n await NativeOidc.setSessionSnapshot({\n [nativeContract.fields.namespace]: this.storageNamespace,\n [nativeContract.fields.value]: user\n ? JSON.stringify(toStoredSession(user, this.settings.authority, this.settings.client_id))\n : null,\n });\n }\n async cancel() {\n await NativeOidc.cancel();\n }\n async dispose() {\n this.stopSilentRenew();\n await this.appStateListener?.remove();\n await this.cancel();\n }\n async signinRedirect() {\n unsupported('Redirect navigation');\n }\n async signinRedirectCallback(_url) {\n unsupported('Redirect navigation');\n }\n async signinCallback(_url) {\n unsupported('Browser callback dispatch');\n }\n async signinResourceOwnerCredentials(_args) {\n unsupported('Resource Owner Password Credentials');\n }\n async signinSilentCallback(_url) {\n unsupported('Iframe navigation');\n }\n async signoutRedirect() {\n unsupported('Redirect navigation');\n }\n async signoutRedirectCallback(_url) {\n unsupported('Redirect navigation');\n }\n async signoutCallback(_url, _keepOpen) {\n unsupported('Browser callback dispatch');\n }\n async signoutSilent() {\n unsupported('Iframe logout');\n }\n async signoutSilentCallback(_url) {\n unsupported('Iframe logout');\n }\n async querySessionStatus() {\n unsupported('Browser session monitoring');\n }\n}\nfunction assertRuntime() {\n if (!globalThis.crypto?.subtle || !globalThis.crypto.getRandomValues) {\n throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, 'Web Crypto is required by oidc-client-ts');\n }\n}\nfunction assertSettings(settings) {\n if ('client_secret' in settings) {\n throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, 'Native public clients must not contain a client secret');\n }\n if ('client_authentication' in settings || 'dpop' in settings) {\n throw new CapacitorOidcError(nativeContract.errorCodes.unsupportedRuntime, 'The supplied settings contain an unsupported native-client option');\n }\n}\nfunction toStoredSession(user, issuer, clientId) {\n return {\n version: 1,\n issuer,\n clientId,\n accessToken: user.access_token,\n refreshToken: user.refresh_token,\n idToken: user.id_token,\n tokenType: user.token_type,\n scope: user.scope,\n expiresAt: user.expires_at,\n };\n}\n//# sourceMappingURL=capacitor-user-manager.js.map"],"names":["registerPlugin","UserManager","App","ErrorResponse"],"mappings":"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"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import AuthenticationServices
|
|
2
|
+
import Capacitor
|
|
3
|
+
import Foundation
|
|
4
|
+
|
|
5
|
+
@objc(CapacitorOidcPlugin)
|
|
6
|
+
public final class CapacitorOidcPlugin: CAPPlugin, CAPBridgedPlugin, ASWebAuthenticationPresentationContextProviding {
|
|
7
|
+
public let identifier = "CapacitorOidcPlugin"
|
|
8
|
+
public let jsName = NativeContract.pluginName
|
|
9
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
10
|
+
CAPPluginMethod(name: NativeContract.Method.configure, returnType: CAPPluginReturnPromise),
|
|
11
|
+
CAPPluginMethod(name: NativeContract.Method.open, returnType: CAPPluginReturnPromise),
|
|
12
|
+
CAPPluginMethod(name: NativeContract.Method.cancel, returnType: CAPPluginReturnPromise),
|
|
13
|
+
CAPPluginMethod(name: NativeContract.Method.storageSet, returnType: CAPPluginReturnPromise),
|
|
14
|
+
CAPPluginMethod(name: NativeContract.Method.storageGet, returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: NativeContract.Method.storageRemove, returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: NativeContract.Method.storageGetAllKeys, returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: NativeContract.Method.setSessionSnapshot, returnType: CAPPluginReturnPromise)
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
private var accessGroup: String?
|
|
21
|
+
private var accessibility = TokenVaultAccessibility.afterFirstUnlockThisDeviceOnly
|
|
22
|
+
private var authenticationSession: ASWebAuthenticationSession?
|
|
23
|
+
private var authenticationCall: CAPPluginCall?
|
|
24
|
+
|
|
25
|
+
@objc public func configure(_ call: CAPPluginCall) {
|
|
26
|
+
accessGroup = call.getString(NativeContract.Field.keychainAccessGroup)
|
|
27
|
+
if call.getString(NativeContract.Field.keychainAccessibility) == NativeContract.KeychainAccessibility.whenUnlockedThisDeviceOnly {
|
|
28
|
+
accessibility = .whenUnlockedThisDeviceOnly
|
|
29
|
+
} else {
|
|
30
|
+
accessibility = .afterFirstUnlockThisDeviceOnly
|
|
31
|
+
}
|
|
32
|
+
call.resolve()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc public func open(_ call: CAPPluginCall) {
|
|
36
|
+
guard authenticationSession == nil else {
|
|
37
|
+
call.reject("An authentication session is already active", NativeContract.ErrorCode.authSessionInProgress)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
guard let urlValue = call.getString(NativeContract.Field.url), let url = URL(string: urlValue),
|
|
41
|
+
let callbackValue = call.getString(NativeContract.Field.callbackUrl), let callbackURL = URL(string: callbackValue),
|
|
42
|
+
let scheme = callbackURL.scheme else {
|
|
43
|
+
call.reject("The authentication URL or callback URL is invalid", NativeContract.ErrorCode.invalidCallback)
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
guard isSecureRequestURL(url) else {
|
|
47
|
+
call.reject("The authentication URL must use HTTPS outside loopback development", NativeContract.ErrorCode.browserUnavailable)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let completion: ASWebAuthenticationSession.CompletionHandler = { [weak self] callbackURL, error in
|
|
52
|
+
DispatchQueue.main.async { self?.completeAuthentication(callbackURL: callbackURL, error: error) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let session: ASWebAuthenticationSession
|
|
56
|
+
if #available(iOS 17.4, *), scheme.lowercased() == "https" {
|
|
57
|
+
guard let host = callbackURL.host else {
|
|
58
|
+
call.reject("The HTTPS callback URL has no host", NativeContract.ErrorCode.invalidCallback)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
session = ASWebAuthenticationSession(
|
|
62
|
+
url: url,
|
|
63
|
+
callback: .https(host: host, path: callbackURL.path),
|
|
64
|
+
completionHandler: completion
|
|
65
|
+
)
|
|
66
|
+
} else {
|
|
67
|
+
guard scheme.lowercased() != "https" else {
|
|
68
|
+
call.reject("HTTPS callbacks require iOS 17.4 or newer", NativeContract.ErrorCode.browserUnavailable)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
session = ASWebAuthenticationSession(url: url, callbackURLScheme: scheme, completionHandler: completion)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
session.presentationContextProvider = self
|
|
75
|
+
session.prefersEphemeralWebBrowserSession = call.getBool(NativeContract.Field.prefersEphemeralWebBrowserSession) ?? false
|
|
76
|
+
authenticationCall = call
|
|
77
|
+
authenticationSession = session
|
|
78
|
+
guard session.start() else {
|
|
79
|
+
authenticationCall = nil
|
|
80
|
+
authenticationSession = nil
|
|
81
|
+
call.reject("The system authentication session could not start", NativeContract.ErrorCode.browserUnavailable)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@objc public func cancel(_ call: CAPPluginCall) {
|
|
87
|
+
let activeCall = authenticationCall
|
|
88
|
+
let activeSession = authenticationSession
|
|
89
|
+
authenticationCall = nil
|
|
90
|
+
authenticationSession = nil
|
|
91
|
+
activeSession?.cancel()
|
|
92
|
+
activeCall?.reject("The user cancelled authentication", NativeContract.ErrorCode.userCancelled)
|
|
93
|
+
call.resolve()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@objc public func storageSet(_ call: CAPPluginCall) {
|
|
97
|
+
guard let namespace = call.getString(NativeContract.Field.namespace), let key = call.getString(NativeContract.Field.key),
|
|
98
|
+
let value = call.getString(NativeContract.Field.value) else {
|
|
99
|
+
call.reject("Storage input is invalid", NativeContract.ErrorCode.secureStorageError)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
resolveStorage(call) { try vault().set(namespace: namespace, key: key, value: value); return [:] }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc public func storageGet(_ call: CAPPluginCall) {
|
|
106
|
+
guard let namespace = call.getString(NativeContract.Field.namespace), let key = call.getString(NativeContract.Field.key) else {
|
|
107
|
+
call.reject("Storage input is invalid", NativeContract.ErrorCode.secureStorageError)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
resolveStorage(call) { [NativeContract.Field.value: try vault().get(namespace: namespace, key: key) as Any] }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@objc public func storageRemove(_ call: CAPPluginCall) {
|
|
114
|
+
guard let namespace = call.getString(NativeContract.Field.namespace), let key = call.getString(NativeContract.Field.key) else {
|
|
115
|
+
call.reject("Storage input is invalid", NativeContract.ErrorCode.secureStorageError)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
resolveStorage(call) { [NativeContract.Field.value: try vault().remove(namespace: namespace, key: key) as Any] }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@objc public func storageGetAllKeys(_ call: CAPPluginCall) {
|
|
122
|
+
guard let namespace = call.getString(NativeContract.Field.namespace) else {
|
|
123
|
+
call.reject("Storage input is invalid", NativeContract.ErrorCode.secureStorageError)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
resolveStorage(call) { [NativeContract.Field.keys: try vault().getAllKeys(namespace: namespace)] }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@objc public func setSessionSnapshot(_ call: CAPPluginCall) {
|
|
130
|
+
guard let namespace = call.getString(NativeContract.Field.namespace) else {
|
|
131
|
+
call.reject("Storage input is invalid", NativeContract.ErrorCode.secureStorageError)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
resolveStorage(call) {
|
|
135
|
+
try vault().setSessionSnapshot(namespace: namespace, value: call.getString(NativeContract.Field.value))
|
|
136
|
+
return [:]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
|
|
141
|
+
bridge?.viewController?.view.window ?? ASPresentationAnchor()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private func completeAuthentication(callbackURL: URL?, error: Error?) {
|
|
145
|
+
let call = authenticationCall
|
|
146
|
+
authenticationCall = nil
|
|
147
|
+
authenticationSession = nil
|
|
148
|
+
|
|
149
|
+
if let sessionError = error as? ASWebAuthenticationSessionError, sessionError.code == .canceledLogin {
|
|
150
|
+
call?.reject("The user cancelled authentication", NativeContract.ErrorCode.userCancelled)
|
|
151
|
+
} else if error != nil {
|
|
152
|
+
call?.reject("The system authentication session failed", NativeContract.ErrorCode.browserUnavailable)
|
|
153
|
+
} else if let callbackURL {
|
|
154
|
+
call?.resolve([NativeContract.Field.url: callbackURL.absoluteString])
|
|
155
|
+
} else {
|
|
156
|
+
call?.reject("The authentication session returned no callback", NativeContract.ErrorCode.invalidCallback)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private func vault() -> TokenVault {
|
|
161
|
+
TokenVault(accessGroup: accessGroup, accessibility: accessibility)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private func isSecureRequestURL(_ url: URL) -> Bool {
|
|
165
|
+
if url.scheme?.lowercased() == "https" { return true }
|
|
166
|
+
guard url.scheme?.lowercased() == "http", let host = url.host?.lowercased() else { return false }
|
|
167
|
+
return host == "localhost" || host == "127.0.0.1" || host == "::1"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func resolveStorage(_ call: CAPPluginCall, operation: () throws -> [String: Any]) {
|
|
171
|
+
do {
|
|
172
|
+
call.resolve(try operation())
|
|
173
|
+
} catch {
|
|
174
|
+
call.reject("Secure storage failed", NativeContract.ErrorCode.secureStorageError)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Generated by scripts/generate-native-contract.mjs. Do not edit.
|
|
2
|
+
|
|
3
|
+
enum NativeContract {
|
|
4
|
+
static let pluginName = "CapacitorOidc"
|
|
5
|
+
|
|
6
|
+
enum Method {
|
|
7
|
+
static let configure = "configure"
|
|
8
|
+
static let open = "open"
|
|
9
|
+
static let cancel = "cancel"
|
|
10
|
+
static let storageSet = "storageSet"
|
|
11
|
+
static let storageGet = "storageGet"
|
|
12
|
+
static let storageRemove = "storageRemove"
|
|
13
|
+
static let storageGetAllKeys = "storageGetAllKeys"
|
|
14
|
+
static let setSessionSnapshot = "setSessionSnapshot"
|
|
15
|
+
}
|
|
16
|
+
enum Field {
|
|
17
|
+
static let url = "url"
|
|
18
|
+
static let callbackUrl = "callbackUrl"
|
|
19
|
+
static let prefersEphemeralWebBrowserSession = "prefersEphemeralWebBrowserSession"
|
|
20
|
+
static let keychainAccessGroup = "keychainAccessGroup"
|
|
21
|
+
static let keychainAccessibility = "keychainAccessibility"
|
|
22
|
+
static let namespace = "namespace"
|
|
23
|
+
static let key = "key"
|
|
24
|
+
static let value = "value"
|
|
25
|
+
static let keys = "keys"
|
|
26
|
+
}
|
|
27
|
+
enum ErrorCode {
|
|
28
|
+
static let authSessionInProgress = "AUTH_SESSION_IN_PROGRESS"
|
|
29
|
+
static let userCancelled = "USER_CANCELLED"
|
|
30
|
+
static let browserUnavailable = "BROWSER_UNAVAILABLE"
|
|
31
|
+
static let invalidCallback = "INVALID_CALLBACK"
|
|
32
|
+
static let secureStorageError = "SECURE_STORAGE_ERROR"
|
|
33
|
+
static let unsupportedRuntime = "UNSUPPORTED_RUNTIME"
|
|
34
|
+
}
|
|
35
|
+
enum KeychainAccessibility {
|
|
36
|
+
static let afterFirstUnlockThisDeviceOnly = "afterFirstUnlockThisDeviceOnly"
|
|
37
|
+
static let whenUnlockedThisDeviceOnly = "whenUnlockedThisDeviceOnly"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public struct StoredSessionV1: Codable, Equatable, Sendable {
|
|
4
|
+
public let version: Int
|
|
5
|
+
public let issuer: String
|
|
6
|
+
public let clientId: String
|
|
7
|
+
public let accessToken: String
|
|
8
|
+
public let refreshToken: String?
|
|
9
|
+
public let idToken: String?
|
|
10
|
+
public let tokenType: String
|
|
11
|
+
public let scope: String?
|
|
12
|
+
public let expiresAt: Int?
|
|
13
|
+
|
|
14
|
+
public init(
|
|
15
|
+
version: Int = 1,
|
|
16
|
+
issuer: String,
|
|
17
|
+
clientId: String,
|
|
18
|
+
accessToken: String,
|
|
19
|
+
refreshToken: String? = nil,
|
|
20
|
+
idToken: String? = nil,
|
|
21
|
+
tokenType: String,
|
|
22
|
+
scope: String? = nil,
|
|
23
|
+
expiresAt: Int? = nil
|
|
24
|
+
) {
|
|
25
|
+
self.version = version
|
|
26
|
+
self.issuer = issuer
|
|
27
|
+
self.clientId = clientId
|
|
28
|
+
self.accessToken = accessToken
|
|
29
|
+
self.refreshToken = refreshToken
|
|
30
|
+
self.idToken = idToken
|
|
31
|
+
self.tokenType = tokenType
|
|
32
|
+
self.scope = scope
|
|
33
|
+
self.expiresAt = expiresAt
|
|
34
|
+
}
|
|
35
|
+
}
|