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

Add support for deletion of an installation (InstallationStore / MemoryInstallationStore) #1272

Merged
merged 3 commits into from Jun 18, 2021
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
13 changes: 13 additions & 0 deletions examples/oauth-v2/app.js
Expand Up @@ -53,6 +53,19 @@ const installer = new InstallProvider({
}
throw new Error('Failed fetching installation');
},
deleteInstallation: async (installQuery) => {
if (installQuery.isEnterpriseInstall) {
if (installQuery.enterpriseId !== undefined) {
// delete org installation
return await keyv.delete(installQuery.enterpriseId)
}
}
if (installQuery.teamId !== undefined) {
// delete single team installation
return await keyv.delete(installQuery.teamId);
}
throw new Error('Failed to delete installation');
},
},
});

Expand Down
19 changes: 17 additions & 2 deletions packages/oauth/README.md
Expand Up @@ -184,16 +184,31 @@ const installer = new InstallProvider({
// returns installation object from database
fetchInstallation: async (installQuery) => {
// replace myDB.get with your own database or OEM getter
if (query.isEnterpriseInstall && query.enterpriseId !== undefined) {
if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {
// org wide app installation lookup
return await myDB.get(installQuery.enterpriseId);
}
if (query.teamId !== undefined) {
if (installQuery.teamId !== undefined) {
// single team app installation lookup
return await myDB.get(installQuery.teamId);
}
throw new Error('Failed fetching installation');
},
// takes in an installQuery as an argument
// installQuery = {teamId: 'string', enterpriseId: 'string', userId: 'string', conversationId: 'string', isEnterpriseInstall: boolean};
// returns nothing
deleteInstallation: async (installQuery) => {
// replace myDB.get with your own database or OEM getter
if (query.isEnterpriseInstall && query.enterpriseId !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you fix the same error for fetchInstallation method?

Suggested change
if (query.isEnterpriseInstall && query.enterpriseId !== undefined) {
if (installQuery.isEnterpriseInstall && installQuery.enterpriseId !== undefined) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on both of these. Will update!

// org wide app installation deletion
return await myDB.delete(installQuery.enterpriseId);
}
if (query.teamId !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (query.teamId !== undefined) {
if (installQuery.teamId !== undefined) {

// single team app installation deletion
return await myDB.delete(installQuery.teamId);
}
throw new Error('Failed to delete installation');
},
},
});
```
Expand Down
20 changes: 19 additions & 1 deletion packages/oauth/src/index.spec.js
Expand Up @@ -74,6 +74,13 @@ const installationStore = {
return new Promise((resolve) => {
resolve(item);
});
},
deleteInstallation: (installQuery) => {
// db delete
delete devDB[installQuery.teamId];
return new Promise((resolve) => {
resolve();
});
}
}

Expand Down Expand Up @@ -450,7 +457,7 @@ describe('OAuth', async () => {
});
});

describe('MemoryInstallStore', async () => {
describe('MemoryInstallationStore', async () => {
it('should store and fetch an installation', async () => {
const installer = new InstallProvider({clientId, clientSecret, stateSecret});
const fakeTeamId = storedInstallation.team.id;
Expand All @@ -460,6 +467,17 @@ describe('OAuth', async () => {
assert.deepEqual(fetchedResult, storedInstallation);
assert.deepEqual(storedInstallation, installer.installationStore.devDB[fakeTeamId]);
});

it('should delete a stored installation', async () => {
const installer = new InstallProvider({clientId, clientSecret, stateSecret});
const fakeTeamId = storedInstallation.team.id;

await installer.installationStore.storeInstallation(storedInstallation);
assert.isNotEmpty(installer.installationStore.devDB);

await installer.installationStore.deleteInstallation({ teamId:fakeTeamId });
assert.isEmpty(installer.installationStore.devDB);
});
});

describe('ClearStateStore', async () => {
Expand Down
29 changes: 29 additions & 0 deletions packages/oauth/src/index.ts
Expand Up @@ -456,9 +456,12 @@ export interface InstallationStore {
logger?: Logger): Promise<void>;
fetchInstallation:
(query: InstallationQuery<boolean>, logger?: Logger) => Promise<Installation<'v1' | 'v2', boolean>>;
deleteInstallation:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider making this optional incase folks have created their own installationStores. By making it required, it would mean their installationStores are invalid when they upgrade.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, great point. Thanks.

(query: InstallationQuery<boolean>, logger?: Logger) => Promise<void>;
}

// using a javascript object as a makeshift database for development
// storing user tokens is not supported
interface DevDatabase {
[teamIdOrEnterpriseId: string]: Installation;
}
Expand Down Expand Up @@ -513,6 +516,32 @@ class MemoryInstallationStore implements InstallationStore {
}
throw new Error('Failed fetching installation');
}

public async deleteInstallation(query: InstallationQuery<boolean>, logger?: Logger): Promise<void> {
if (logger !== undefined) {
logger.warn('Deleting Access Token from DB. Please use a real Installation Store for production!');
}

if (query.isEnterpriseInstall && query.enterpriseId !== undefined) {
if (logger !== undefined) {
logger.debug('deleting org installation');
}

const { [query.enterpriseId]: _, ...devDB } = this.devDB;
this.devDB = devDB;

} else if (query.teamId !== undefined) {
if (logger !== undefined) {
logger.debug('deleting single team installation');
}

const { [query.teamId]: _, ...devDB } = this.devDB;
this.devDB = devDB;

} else {
throw new Error('Failed to delete installation');
}
}
}

/**
Expand Down