Skip to content

Commit 8a3bcd7

Browse files
feat: add step-up bridge (stepUp / isSteppedUp)
Expose the native step-up (re-)authentication API through the Ionic Capacitor plugin, matching the React Native and Flutter wrappers. - TS: stepUp(payload?) / isSteppedUp(payload?) on the plugin interface, web stubs, and FronteggService convenience wrappers. - iOS: honors the optional maxAge freshness window (seconds). - Android: forwards to the native SDK; maxAge is not yet passed through because the native isSteppedUp/stepUp take a Kotlin Duration that cannot be constructed from the Java plugin (documented in the API).
1 parent 07a7f4a commit 8a3bcd7

6 files changed

Lines changed: 107 additions & 0 deletions

File tree

android/src/main/java/com/frontegg/ionic/FronteggNativePlugin.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,4 +392,33 @@ public void openAdminPortal(PluginCall call) {
392392
call.resolve();
393393
}
394394

395+
@PluginMethod
396+
public void isSteppedUp(PluginCall call) {
397+
// NOTE: `maxAge` is honored on iOS but not yet forwarded here — the native
398+
// isSteppedUp(Duration?) takes a Kotlin Duration (an inline value class) that cannot be
399+
// constructed from Java. Passing null checks ACR/AMR without the freshness window until a
400+
// native Java-friendly overload is added.
401+
boolean result = FronteggAppKt.getFronteggAuth(this.getContext()).isSteppedUp(null);
402+
JSObject ret = new JSObject();
403+
ret.put("isSteppedUp", result);
404+
call.resolve(ret);
405+
}
406+
407+
@PluginMethod
408+
public void stepUp(PluginCall call) {
409+
if (this.getActivity() == null) {
410+
call.reject("NO_ACTIVITY", "Cannot start step-up without an active Activity");
411+
return;
412+
}
413+
// `maxAge` not forwarded on Android — see isSteppedUp note above.
414+
FronteggAppKt.getFronteggAuth(this.getContext()).stepUp(this.getActivity(), null, (error) -> {
415+
if (error != null) {
416+
call.reject(error.getMessage());
417+
} else {
418+
call.resolve();
419+
}
420+
return null;
421+
});
422+
}
423+
395424
}

ios/Plugin/FronteggNativePlugin.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
CAP_PLUGIN_METHOD(getFeatureEntitlement, CAPPluginReturnPromise);
1717
CAP_PLUGIN_METHOD(getPermissionEntitlement, CAPPluginReturnPromise);
1818
CAP_PLUGIN_METHOD(openAdminPortal, CAPPluginReturnPromise);
19+
CAP_PLUGIN_METHOD(stepUp, CAPPluginReturnPromise);
20+
CAP_PLUGIN_METHOD(isSteppedUp, CAPPluginReturnPromise);
1921
)
2022

2123

ios/Plugin/FronteggNativePlugin.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,27 @@ public class FronteggNativePlugin: CAPPlugin {
324324
}
325325
}
326326

327+
@objc func isSteppedUp(_ call: CAPPluginCall) {
328+
// `maxAge` is optional and expressed in seconds (TimeInterval); nil uses the SDK default.
329+
let maxAge = call.getDouble("maxAge")
330+
call.resolve(["isSteppedUp": fronteggApp.auth.isSteppedUp(maxAge: maxAge)])
331+
}
332+
333+
@objc func stepUp(_ call: CAPPluginCall) {
334+
let maxAge = call.getDouble("maxAge")
335+
let completion: FronteggAuth.CompletionHandler = { result in
336+
switch result {
337+
case .success:
338+
call.resolve()
339+
case .failure(let error):
340+
call.reject(error.failureReason ?? error.localizedDescription, nil, error)
341+
}
342+
}
343+
Task {
344+
await self.fronteggApp.auth.stepUp(maxAge: maxAge, completion)
345+
}
346+
}
347+
327348
private static func topViewController() -> UIViewController? {
328349
let keyWindow = UIApplication.shared.connectedScenes
329350
.compactMap { $0 as? UIWindowScene }

src/definitions.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,27 @@ export interface FronteggNativePlugin {
222222
* @returns A promise that resolves when the portal is presented.
223223
*/
224224
openAdminPortal(): Promise<void>;
225+
226+
/**
227+
* Starts a step-up (re-)authentication: the user re-verifies with a stronger factor
228+
* (MFA) before a sensitive action. Resolves once step-up completes (rejects on failure).
229+
*
230+
* @param payload.maxAge - Optional freshness window, in **seconds**. If the user
231+
* authenticated more recently than this, step-up may be skipped.
232+
*
233+
* Note: `maxAge` is honored on **iOS**. On **Android** it is not yet forwarded (the native
234+
* API takes a Kotlin `Duration` that cannot be constructed from the Java plugin); Android
235+
* uses the server default freshness window until a native Java-friendly overload lands.
236+
*/
237+
stepUp(payload?: { maxAge?: number }): Promise<void>;
238+
239+
/**
240+
* Returns whether the current session is already "stepped up" (has a recent MFA/ACR).
241+
*
242+
* @param payload.maxAge - Optional freshness window, in **seconds** (see `stepUp` for the
243+
* Android `maxAge` caveat).
244+
*/
245+
isSteppedUp(payload?: { maxAge?: number }): Promise<{ isSteppedUp: boolean }>;
225246
}
226247

227248
/**

src/frontegg.service.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,22 @@ export class FronteggService {
316316
public openAdminPortal(): Promise<void> {
317317
return FronteggNative.openAdminPortal();
318318
}
319+
320+
/**
321+
* Start a step-up (re-)authentication. Resolves when step-up completes.
322+
* @param maxAge Optional freshness window in seconds (honored on iOS; see docs for the
323+
* Android caveat).
324+
*/
325+
public stepUp(maxAge?: number): Promise<void> {
326+
return FronteggNative.stepUp({ maxAge });
327+
}
328+
329+
/**
330+
* Whether the current session is already stepped up.
331+
* @param maxAge Optional freshness window in seconds.
332+
*/
333+
public async isSteppedUp(maxAge?: number): Promise<boolean> {
334+
const { isSteppedUp } = await FronteggNative.isSteppedUp({ maxAge });
335+
return isSteppedUp;
336+
}
319337
}

src/web.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,20 @@ export class FronteggNativeWeb
8686
async openAdminPortal(): Promise<void> {
8787
throw Error('FronteggNative.openAdminPortal not implemented in web');
8888
}
89+
90+
async stepUp(payload?: { maxAge?: number }): Promise<void> {
91+
throw Error(
92+
`FronteggNative.stepUp ${JSON.stringify(payload)} not implemented in web`,
93+
);
94+
}
95+
96+
async isSteppedUp(payload?: {
97+
maxAge?: number;
98+
}): Promise<{ isSteppedUp: boolean }> {
99+
throw Error(
100+
`FronteggNative.isSteppedUp ${JSON.stringify(
101+
payload,
102+
)} not implemented in web`,
103+
);
104+
}
89105
}

0 commit comments

Comments
 (0)