зеркало из https://github.com/mozilla/fxa.git
Merge pull request #5578 from mozilla/feat/issue-5398
feat(gql-api): add attached client revoke
This commit is contained in:
Коммит
e18d2bd8dc
|
@ -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);
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче