Skip to content

Commit

Permalink
fix: idlemanager starting before login suceeds (#646)
Browse files Browse the repository at this point in the history
* bug: idlemanager does not get initialized until login

* changelog
  • Loading branch information
krpeacock committed Oct 18, 2022
1 parent e138b28 commit 0ff62dc
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 21 deletions.
4 changes: 4 additions & 0 deletions docs/generated/changelog.html
Expand Up @@ -15,6 +15,10 @@ <h2>Version x.x.x</h2>
<li>chore: configures unpkg to use esmodules</li>
<li>chore: removes unused lint shell script</li>
<li>chore: adds js-sha256 dependency to principal</li>
<li>
bug: fixes idlemanager initializing - now either requires createOptions.identity or
authClient.login to be called before starting idle timeout
</li>
</ul>
<h2>Version 0.14.0</h2>
<ul>
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 80 additions & 15 deletions packages/auth-client/src/index.test.ts
Expand Up @@ -70,9 +70,14 @@ describe('Auth Client', () => {
expect(await test.isAuthenticated()).toBe(false);
expect(test.getIdentity().getPrincipal().isAnonymous()).toBe(true);
});
it('should initialize an idleManager', async () => {
it('should not initialize an idleManager if the user is not logged in', async () => {
const test = await AuthClient.create();
expect(test.idleManager).not.toBeDefined();
});
it('should initialize an idleManager if an identity is passed', async () => {
const test = await AuthClient.create({ identity: await Ed25519KeyIdentity.generate() });
expect(test.idleManager).toBeDefined();
test.idleManager; //?
});
it('should be able to invalidate an identity after going idle', async () => {
// setup actor
Expand Down Expand Up @@ -141,14 +146,6 @@ describe('Auth Client', () => {
});
delete (window as any).location;
(window as any).location = { reload: jest.fn(), fetch };
const mockFetch: jest.Mock = jest.fn();

const canisterId = Principal.fromText('2chl6-4hpzw-vqaaa-aaaaa-c');
const actorInterface = () => {
return IDL.Service({
greet: IDL.Func([IDL.Text], [IDL.Text]),
});
};

const storage: AuthClientStorage = {
remove: jest.fn(),
Expand All @@ -165,14 +162,14 @@ describe('Auth Client', () => {
});

// Test login flow
await test.login({ identityProvider: 'http://localhost' });
const onSuccess = jest.fn();
test.login({ onSuccess });

idpMock.ready();

expect(storage.set).toBeCalled();
expect(storage.remove).not.toBeCalled();

const httpAgent = new HttpAgent({ fetch: mockFetch, host: 'http://127.0.0.1:8000' });
const actor = Actor.createActor(actorInterface, { canisterId, agent: httpAgent });

// simulate user being inactive for 10 minutes
jest.advanceTimersByTime(10 * 60 * 1000);

Expand Down Expand Up @@ -218,7 +215,8 @@ describe('Auth Client', () => {
});

// Test login flow
await test.login({ identityProvider: 'http://localhost' });
await test.login();
idpMock.ready();

expect(storage.set).toBeCalled();
expect(storage.remove).not.toBeCalled();
Expand All @@ -232,6 +230,24 @@ describe('Auth Client', () => {
expect(window.location.reload).not.toBeCalled();
});
it('should not reload the page if a callback is provided', async () => {
setup({
onAuthRequest: () => {
// Send a valid request.
idpMock.send({
kind: 'authorize-client-success',
delegations: [
{
delegation: {
pubkey: Uint8Array.from([]),
expiration: BigInt(0),
},
signature: Uint8Array.from([]),
},
],
userPublicKey: Uint8Array.from([]),
});
},
});
delete (window as any).location;
(window as any).location = { reload: jest.fn(), fetch };
const idleCb = jest.fn();
Expand All @@ -242,6 +258,9 @@ describe('Auth Client', () => {
},
});

test.login();
idpMock.ready();

// simulate user being inactive for 10 minutes
jest.advanceTimersByTime(10 * 60 * 1000);

Expand Down Expand Up @@ -325,6 +344,52 @@ describe('Auth Client', () => {
jest.advanceTimersByTime(30 * 60 * 1000);
expect(idleFn).not.toHaveBeenCalled();
});
it('should not set up an idle timer if the client is not logged in', async () => {
setup({
onAuthRequest: () => {
// Send a valid request.
idpMock.send({
kind: 'authorize-client-success',
delegations: [
{
delegation: {
pubkey: Uint8Array.from([]),
expiration: BigInt(0),
},
signature: Uint8Array.from([]),
},
],
userPublicKey: Uint8Array.from([]),
});
},
});
delete (window as any).location;
(window as any).location = { reload: jest.fn(), fetch };

const storage: AuthClientStorage = {
remove: jest.fn(),
get: jest.fn(),
set: jest.fn(),
};

const test = await AuthClient.create({
storage,
idleOptions: {
idleTimeout: 1000,
},
});

expect(storage.set).toBeCalled();
expect(storage.remove).not.toBeCalled();

// simulate user being inactive for 10 minutes
jest.advanceTimersByTime(10 * 60 * 1000);

// Storage should not be cleared
expect(storage.remove).not.toBeCalled();
// Page should not be reloaded
expect(window.location.reload).not.toBeCalled();
});
});

describe('IdbStorage', () => {
Expand Down Expand Up @@ -527,7 +592,7 @@ describe('Auth Client login', () => {

const client = await AuthClient.create();
const onSuccess = jest.fn();
await client.login({ onSuccess: onSuccess });
client.login({ onSuccess: onSuccess });

idpMock.ready();

Expand Down
24 changes: 20 additions & 4 deletions packages/auth-client/src/index.ts
Expand Up @@ -252,9 +252,14 @@ export class AuthClient {
key = null;
}
}
const idleManager = options.idleOptions?.disableIdle
? undefined
: IdleManager.create(options.idleOptions);
let idleManager: IdleManager | undefined = undefined;
if (options.idleOptions?.disableIdle) {
idleManager = undefined;
}
// if there is a delegation chain or provided identity, setup idleManager
else if (chain || options.identity) {
idleManager = IdleManager.create(options.idleOptions);
}

if (!key) {
// Create a new key (whether or not one was in storage).
Expand All @@ -270,7 +275,7 @@ export class AuthClient {
private _key: SignIdentity,
private _chain: DelegationChain | null,
private _storage: AuthClientStorage,
public readonly idleManager: IdleManager | undefined,
public idleManager: IdleManager | undefined,
private _createOptions: AuthClientCreateOptions | undefined,
// A handle on the IdP window.
private _idpWindow?: Window,
Expand Down Expand Up @@ -317,6 +322,17 @@ export class AuthClient {
this._identity = DelegationIdentity.fromDelegation(key, this._chain);

this._idpWindow?.close();
if (!this.idleManager) {
const idleOptions = this._createOptions?.idleOptions;
this.idleManager = IdleManager.create(idleOptions);

if (!idleOptions?.onIdle && !idleOptions?.disableDefaultIdleCallback) {
this.idleManager?.registerCallback(() => {
this.logout();
location.reload();
});
}
}
onSuccess?.();
this._removeEventListener();
delete this._idpWindow;
Expand Down
2 changes: 1 addition & 1 deletion packages/bls-verify/tsconfig.json
Expand Up @@ -18,6 +18,6 @@
"strict": true,
"target": "es2017"
},
"include": ["src/**/*"],
"include": ["src/**/*", "types/**/*", "amcl-js-3.0.0.tgz"],
"references": []
}
1 change: 1 addition & 0 deletions packages/bls-verify/types/amcl.d.ts
@@ -0,0 +1 @@
declare module 'amcl-js';

0 comments on commit 0ff62dc

Please sign in to comment.