Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: idlemanager starting before login suceeds #646

Merged
merged 3 commits into from Oct 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/generated/changelog.html
Expand Up @@ -13,6 +13,10 @@ <h1>Agent-JS Changelog</h1>
<h2>Version x.x.x</h2>
<ul>
<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';