Merge pull request #5578 from mozilla/feat/issue-5398

feat(gql-api): add attached client revoke
This commit is contained in:
Ben Bangert 2020-06-08 13:52:52 -07:00 коммит произвёл GitHub
Родитель e6fafda68b adb832f79d
Коммит e18d2bd8dc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 96 добавлений и 3 удалений

Просмотреть файл

@ -45,6 +45,10 @@ export class AuthServerSource extends DataSource {
return this.authClient.attachedClients(this.token);
}
public attachedClientDestroy(clientInfo: any): Promise<any> {
return this.authClient.attachedClientDestroy(this.token, clientInfo);
}
public totp(): Promise<any> {
return this.authClient.checkTotpTokenExists(this.token);
}

Просмотреть файл

@ -33,6 +33,7 @@ import {
UpdateAvatarPayload,
UpdateDisplayNamePayload,
} from './types/payload';
import { AttachedClientDisconnectInput } from './types/input/attached-client-disconnect';
@Resolver((of) => AccountType)
export class AccountResolver {
@ -122,6 +123,19 @@ export class AccountResolver {
return { clientMutationId: input.clientMutationId };
}
@Mutation((returns) => BasicPayload, {
description:
"Destroy all tokens held by a connected client, disconnecting it from the user's account.",
})
public async attachedClientDisconnect(
@Ctx() context: Context,
@Arg('input', (type) => AttachedClientDisconnectInput)
input: AttachedClientDisconnectInput
) {
await context.dataSources.authAPI.attachedClientDestroy(input);
return { clientMutationId: input.clientMutationId };
}
@Query((returns) => AccountType, { nullable: true })
public account(@Ctx() context: Context, @Info() info: GraphQLResolveInfo) {
context.logger.info('account', { uid: context.authUser });

Просмотреть файл

@ -0,0 +1,40 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { Field, InputType } from 'type-graphql';
@InputType()
export class AttachedClientDisconnectInput {
@Field({
description: 'A unique identifier for the client performing the mutation.',
nullable: true,
})
public clientMutationId?: string;
@Field({
description: 'The OAuth client_id of the connected application.',
nullable: true,
})
public clientId!: string;
@Field({
description: 'The id of the sessionToken held by that client, if any.',
nullable: true,
})
public sessionTokenId!: string;
@Field({
description:
'The id of the OAuth refreshToken held by that client, if any.',
nullable: true,
})
public refreshTokenId!: string;
@Field({
description:
"The id of the client's device record, if it has registered one.",
nullable: true,
})
public deviceId!: string;
}

Просмотреть файл

@ -31,6 +31,7 @@ describe('accountResolver', () => {
await (Account as any).query().insertGraph({ ...USER_1 });
schema = await buildSchema({
resolvers: [AccountResolver],
validate: false,
});
});
@ -92,6 +93,29 @@ describe('accountResolver', () => {
});
describe('mutation', () => {
describe('attachedClientDisconnect', () => {
it('succeeds', async () => {
context.dataSources.authAPI.attachedClientDestroy.resolves(true);
const query = `mutation {
attachedClientDisconnect(input: {clientMutationId: "testid", clientId: "client1234", sessionTokenId: "sesssion1234", refreshTokenId: "refresh1234", deviceId: "device1234"}) {
clientMutationId
}
}`;
context.authUser = USER_1.uid;
const result = (await graphql(
schema,
query,
undefined,
context
)) as any;
assert.isDefined(result.data);
assert.isDefined(result.data.attachedClientDisconnect);
assert.deepEqual(result.data.attachedClientDisconnect, {
clientMutationId: 'testid',
});
});
});
describe('updateDisplayName', () => {
it('succeeds', async () => {
context.dataSources.profileAPI.updateDisplayName.resolves(true);

17
types/fxa-js-client/index.d.ts поставляемый
Просмотреть файл

@ -13,6 +13,7 @@ declare namespace FxAccountClient {
sessionToken: string
): Promise<{ state: string; uid: string }>;
attachedClients(sessionToken: string): Promise<any[]>;
attachedClientDestroy(sessionToken: string, clientInfo: any): Promise<any>;
checkTotpTokenExists(
sessionToken: string
): Promise<{ exists: boolean; verified: boolean }>;
@ -21,7 +22,11 @@ declare namespace FxAccountClient {
createOAuthToken(
sessionToken: string,
clientId: string,
options?: { scope?: string; ttl?: number; access_type?: 'online' | 'offline' }
options?: {
scope?: string;
ttl?: number;
access_type?: 'online' | 'offline';
}
): Promise<{
access_token: string;
refresh_token?: string;
@ -33,7 +38,13 @@ declare namespace FxAccountClient {
}>;
recoveryEmailCreate(sessionToken: string, email: string): Promise<any>;
recoveryEmailDestroy(sessionToken: string, email: string): Promise<any>;
recoveryEmailSetPrimaryEmail(sessionToken: string, email: string): Promise<any>;
recoveryEmailSecondaryResendCode(sessionToken: string, email: string): Promise<any>;
recoveryEmailSetPrimaryEmail(
sessionToken: string,
email: string
): Promise<any>;
recoveryEmailSecondaryResendCode(
sessionToken: string,
email: string
): Promise<any>;
}
}