diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html
index 6b3ead8fd..138919cb3 100644
--- a/docs/generated/changelog.html
+++ b/docs/generated/changelog.html
@@ -22,6 +22,7 @@
Version 0.10.5
of logging the user out and prompting re-authentication. See the @dfinity/auth-client
Readme for more details
+ Reduces the maxTimeToLive default setting from 24 hours to 8
Version 0.10.3
diff --git a/packages/auth-client/README.md b/packages/auth-client/README.md
index 8c28892d0..7782c7606 100644
--- a/packages/auth-client/README.md
+++ b/packages/auth-client/README.md
@@ -1,5 +1,7 @@
# @dfinity/auth-client
+> 0.10.5 Idle update - see changes [here](#0.10.5-idle-update)
+
Simple interface to get your web application authenticated with the Internet Identity Service
Visit the [Dfinity Forum](https://forum.dfinity.org/) and [SDK Documentation](https://sdk.dfinity.org/docs/index.html) for more information and support building on the Internet Computer.
@@ -32,8 +34,10 @@ The authClient can log in with
```js
authClient.login({
+ // 7 days in nanoseconds
+ maxTimeToLive: BigInt(7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
onSuccess: async () => {
- // authClient now has an identity
+ handleAuthenticated(authClient);
},
});
```
@@ -52,15 +56,21 @@ const actor = Actor.createActor(idlFactory, {
});
```
-## Idle Timeout
+Idle Update
+
+As of 0.10.5, the authClient has two notable new features:
+
+1. the maxTimeToLive is now a set to 8 hours by default, down from 24.
+2. you can now set a timeout for when your identity will be considered idle
-As of 0.10.5, you can now set a timeout for when your identity will be considered idle, and you can use that to log out or prompt your user to refresh their authentication. This is recommended for applications managing tokens or other valuable information.
+These defaults are more conservative, out of the interest of protecting users as more sites are starting to manage ICP and NFT's. You can override these defaults, and opt out of the Idle Manager if you so choose. For more details, see the [forum discussion](https://forum.dfinity.org/t/authclient-update-idle-timeouts).
+
+Additionally, we now support utility methods in Agents to invalidate an identity. It is suggested that you use this method to invalidate an identity once the user goes idle by calling `Actor.getAgent(actor).invalidateIdentity()`. See the below code for an example:
```js
const authClient = await AuthClient.create({
idleOptions: {
- idleTimeout: 1000 * 60 * 30, // default is 30 minutes
- disableIdle: false, // set to true to disable idle timeout
+ idleTimeout: 1000 * 60 * 30, // set to 30 minutes
}
});
// ...authClient.login()
@@ -72,14 +82,17 @@ const actor = Actor.createActor(idlFactory, {
canisterId,
});
-authClient.registerActor("ii", actor);
-
refreshLogin() {
// prompt the user then refresh their authentication
- authClient.login();
+ authClient.login({
+ onSuccess: async () => {
+ const newIdentity = await AuthClient.getIdentity();
+ Actor.getAgent(actor).replaceIdentity(newIdentity);
+ }
+ });
}
-authClient.idleManager?.registerCallback?.(refreshLogin)
+authClient.idleManager?.registerCallback?.(refreshLogin);
```
In this code, we create an `authClient` with an idle timeout of 30 minutes. When the user is idle, we invalidate their identity and prompt them to login again.
diff --git a/packages/auth-client/src/idleManager.test.ts b/packages/auth-client/src/idleManager.test.ts
index 3b7ecb986..fa1d55a9c 100644
--- a/packages/auth-client/src/idleManager.test.ts
+++ b/packages/auth-client/src/idleManager.test.ts
@@ -30,7 +30,7 @@ describe('IdleManager tests', () => {
// simulate user being inactive for 9 minutes
jest.advanceTimersByTime(9 * 60 * 1000);
expect(cb).not.toHaveBeenCalled();
- document.dispatchEvent(new KeyboardEvent('keypress'));
+ document.dispatchEvent(new KeyboardEvent('keydown'));
// wait 5 minutes
jest.advanceTimersByTime(5 * 60 * 1000);
diff --git a/packages/auth-client/src/idleManager.ts b/packages/auth-client/src/idleManager.ts
index fc32d9a66..08f71a7b4 100644
--- a/packages/auth-client/src/idleManager.ts
+++ b/packages/auth-client/src/idleManager.ts
@@ -1,3 +1,4 @@
+/** @module IdleManager */
type IdleCB = () => unknown;
export type IdleManagerOptions = {
/**
@@ -21,12 +22,11 @@ export type IdleManagerOptions = {
scrollDebounce?: number;
};
-const events = ['mousedown', 'mousemove', 'keypress', 'touchstart', 'wheel'];
+const events = ['mousedown', 'mousemove', 'keydown', 'touchstart', 'wheel'];
/**
- * Detects if the user has been idle for a duration of `idleTimeout` ms, and calls `onIdle`.
- *
- * @param {IdleManagerOptions} options
+ * Detects if the user has been idle for a duration of `idleTimeout` ms, and calls `onIdle` and registered callbacks.
+ * @see {@link IdleManager}
*/
class IdleManager {
callbacks: IdleCB[] = [];
@@ -34,9 +34,10 @@ class IdleManager {
timeoutID?: number = undefined;
/**
- * Creates an idle manager
+ * Creates an {@link IdleManager}
* @param {IdleManagerOptions} options Optional configuration
- * @param options.onIdle Callback once user has been idle. Use to prompt for fresh login, and use Actor.agentOf(your_actor).invalidateIdentity() to protect the user
+ * @see {@link IdleManagerOptions}
+ * @param options.onIdle Callback once user has been idle. Use to prompt for fresh login, and use `Actor.agentOf(your_actor).invalidateIdentity()` to protect the user
* @param options.idleTimeout timeout in ms
* @param options.captureScroll capture scroll events
* @param options.scrollDebounce scroll debounce time in ms
@@ -45,8 +46,9 @@ class IdleManager {
options: {
/**
* Callback after the user has gone idle
+ * @see {@link IdleCB}
*/
- onIdle?: IdleCB;
+ onIdle?: () => unknown;
/**
* timeout in ms
* @default 10 minutes [600_000]
@@ -67,17 +69,21 @@ class IdleManager {
return new this(options);
}
+ /**
+ * @protected
+ * @param options {@link IdleManagerOptions}
+ */
protected constructor(options: IdleManagerOptions = {}) {
const { onIdle, idleTimeout = 10 * 60 * 1000 } = options || {};
this.callbacks = onIdle ? [onIdle] : [];
this.idleTimeout = idleTimeout;
- const resetTimer = this.resetTimer.bind(this);
+ const _resetTimer = this._resetTimer.bind(this);
- window.addEventListener('load', resetTimer, true);
+ window.addEventListener('load', _resetTimer, true);
events.forEach(function (name) {
- document.addEventListener(name, resetTimer, true);
+ document.addEventListener(name, _resetTimer, true);
});
// eslint-disable-next-line @typescript-eslint/ban-types
@@ -97,16 +103,15 @@ class IdleManager {
if (options?.captureScroll) {
// debounce scroll events
- const scroll = debounce(resetTimer, options?.scrollDebounce ?? 100);
+ const scroll = debounce(_resetTimer, options?.scrollDebounce ?? 100);
window.addEventListener('scroll', scroll, true);
}
- resetTimer();
+ _resetTimer();
}
/**
- * adds a callback to the list of callbacks
- * @param {IdleCB} callback
+ * @param {IdleCB} callback function to be called when user goes idle
*/
public registerCallback(callback: IdleCB): void {
this.callbacks.push(callback);
@@ -118,15 +123,18 @@ class IdleManager {
public exit(): void {
this.callbacks.forEach(cb => cb());
clearTimeout(this.timeoutID);
- window.removeEventListener('load', this.resetTimer, true);
+ window.removeEventListener('load', this._resetTimer, true);
- const resetTimer = this.resetTimer.bind(this);
+ const _resetTimer = this._resetTimer.bind(this);
events.forEach(function (name) {
- document.removeEventListener(name, resetTimer, true);
+ document.removeEventListener(name, _resetTimer, true);
});
}
- resetTimer(): void {
+ /**
+ * Resets the timeouts during cleanup
+ */
+ _resetTimer(): void {
const exit = this.exit.bind(this);
window.clearTimeout(this.timeoutID);
this.timeoutID = window.setTimeout(exit, this.idleTimeout);
diff --git a/packages/auth-client/src/index.ts b/packages/auth-client/src/index.ts
index 30ff2efec..c9c794032 100644
--- a/packages/auth-client/src/index.ts
+++ b/packages/auth-client/src/index.ts
@@ -1,3 +1,4 @@
+/** @module AuthClient */
import {
Actor,
AnonymousIdentity,
@@ -46,7 +47,7 @@ export interface AuthClientCreateOptions {
export interface IdleOptions extends IdleManagerOptions {
/**
- * Disables idle functionality
+ * Disables idle functionality for {@link IdleManager}
* @default false
*/
disableIdle?: boolean;
@@ -54,11 +55,13 @@ export interface IdleOptions extends IdleManagerOptions {
export interface AuthClientLoginOptions {
/**
- * Identity provider. By default, use the identity service.
+ * Identity provider
+ * @default "https://identity.ic0.app"
*/
identityProvider?: string | URL;
/**
* Expiration of the authentication in nanoseconds
+ * @default BigInt(8) hours * BigInt(3_600_000_000_000) nanoseconds
*/
maxTimeToLive?: bigint;
/**
@@ -170,8 +173,48 @@ interface AuthResponseFailure {
type IdentityServiceResponseMessage = AuthReadyMessage | AuthResponse;
type AuthResponse = AuthResponseSuccess | AuthResponseFailure;
+/**
+ * Tool to manage authentication and identity
+ * @see {@link AuthClient}
+ */
export class AuthClient {
- public static async create(options: AuthClientCreateOptions = {}): Promise {
+ /**
+ * Create an AuthClient to manage authentication and identity
+ * @constructs {@link AuthClient}
+ * @param {AuthClientCreateOptions} options
+ * @see {@link AuthClientCreateOptions}
+ * @param options.identity Optional Identity to use as the base
+ * @see {@link SignIdentity}
+ * @param options.storage Storage mechanism for delegration credentials
+ * @see {@link AuthClientStorage}
+ * @param {IdleOptions} options.idleOptions Configures an {@link IdleManager}
+ * @see {@link IdleOptions}
+ * @example
+ * const authClient = await AuthClient.create({
+ * idleOptions: {
+ * disableIdle: true
+ * }
+ * })
+ */
+ public static async create(
+ options: {
+ /**
+ * An {@link Identity} to use as the base.
+ * By default, a new {@link AnonymousIdentity}
+ */
+ identity?: SignIdentity;
+ /**
+ * {@link AuthClientStorage}
+ * @description Optional storage with get, set, and remove. Uses {@link LocalStorage} by default
+ */
+ storage?: AuthClientStorage;
+ /**
+ * Options to handle idle timeouts
+ * @default after 10 minutes, invalidates the identity
+ */
+ idleOptions?: IdleOptions;
+ } = {},
+ ): Promise {
const storage = options.storage ?? new LocalStorage('ic-');
let key: null | SignIdentity = null;
@@ -278,7 +321,47 @@ export class AuthClient {
return !this.getIdentity().getPrincipal().isAnonymous() && this._chain !== null;
}
- public async login(options?: AuthClientLoginOptions): Promise {
+ /**
+ * AuthClient Login -
+ * Opens up a new window to authenticate with Internet Identity
+ * @param {AuthClientLoginOptions} options
+ * @param options.identityProvider Identity provider
+ * @param options.maxTimeToLive Expiration of the authentication in nanoseconds
+ * @param options.onSuccess Callback once login has completed
+ * @param options.onError Callback in case authentication fails
+ * @example
+ * const authClient = await AuthClient.create();
+ * authClient.login({
+ * identityProvider: 'http://.localhost:8000',
+ * maxTimeToLive: BigInt (7) * BigInt(24) * BigInt(3_600_000_000_000) // 1 week
+ * onSuccess: () => {
+ * console.log('Login Successful!');
+ * },
+ * onError: (error) => {
+ * console.error('Login Failed: ', error);
+ * }
+ * });
+ */
+ public async login(options?: {
+ /**
+ * Identity provider
+ * @default "https://identity.ic0.app"
+ */
+ identityProvider?: string | URL;
+ /**
+ * Expiration of the authentication in nanoseconds
+ * @default BigInt(8) hours * BigInt(3_600_000_000_000) nanoseconds
+ */
+ maxTimeToLive?: bigint;
+ /**
+ * Callback once login has completed
+ */
+ onSuccess?: (() => void) | (() => Promise);
+ /**
+ * Callback in case authentication fails
+ */
+ onError?: ((error?: string) => void) | ((error?: string) => Promise);
+ }): Promise {
let key = this._key;
if (!key) {
// Create a new key (whether or not one was in storage).
@@ -287,9 +370,8 @@ export class AuthClient {
await this._storage.set(KEY_LOCALSTORAGE_KEY, JSON.stringify(key));
}
- // Set default maxTimeToLive to 1 day
- const defaultTimeToLive =
- /* days */ BigInt(1) * /* hours */ BigInt(24) * /* nanoseconds */ BigInt(3600000000000);
+ // Set default maxTimeToLive to 8 hours
+ const defaultTimeToLive = /* hours */ BigInt(8) * /* nanoseconds */ BigInt(3_600_000_000_000);
// Create the URL of the IDP. (e.g. https://XXXX/#authorize)
const identityProviderUrl = new URL(
@@ -305,7 +387,7 @@ export class AuthClient {
// Add an event listener to handle responses.
this._eventHandler = this._getEventHandler(identityProviderUrl, {
- maxTimeToLive: defaultTimeToLive,
+ maxTimeToLive: options?.maxTimeToLive ?? defaultTimeToLive,
...options,
});
window.addEventListener('message', this._eventHandler);