Skip to content

Commit

Permalink
Simplified authentication GraphQL: single entry point and serialized …
Browse files Browse the repository at this point in the history
…credentials specific for chosen login method
  • Loading branch information
zaychenko-sergei committed Aug 25, 2023
1 parent 26d115c commit 39503a7
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 166 deletions.
3 changes: 1 addition & 2 deletions resources/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ type AttachmentsEmbedded {
}

type AuthMut {
passwordLogin(login: String!, password: String!): LoginResponse!
githubLogin(code: String!): LoginResponse!
login(loginMethod: String!, loginCredentialsJson: String!): LoginResponse!
accountInfo(accessToken: String!): AccountInfo!
}

Expand Down
11 changes: 11 additions & 0 deletions src/app/api/auth.api.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const LOGIN_METHOD_PASSWORD = "password";
export const LOGIN_METHOD_GITHUB = "oauth_github";

export interface PasswordLoginCredentials {
login: string;
password: string;
}

export interface GithubLoginCredentials {
code: string;
}
41 changes: 21 additions & 20 deletions src/app/api/auth.api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { fakeAsync, flush, TestBed, tick } from "@angular/core/testing";
import { Apollo } from "apollo-angular";
import { AuthApi } from "./auth.api";
import {
AccountDetailsFragment,
FetchAccountInfoDocument,
GithubLoginDocument,
PasswordLoginDocument,
} from "./kamu.graphql.interface";
import { AccountDetailsFragment, FetchAccountInfoDocument, LoginDocument } from "./kamu.graphql.interface";
import { ApolloTestingController, ApolloTestingModule } from "apollo-angular/testing";
import {
mockAccountDetails,
Expand All @@ -21,6 +16,12 @@ import {
} from "./mock/auth.mock";
import { AuthenticationError } from "../common/errors";
import { first } from "rxjs/operators";
import {
GithubLoginCredentials,
LOGIN_METHOD_GITHUB,
LOGIN_METHOD_PASSWORD,
PasswordLoginCredentials,
} from "./auth.api.model";

describe("AuthApi", () => {
let service: AuthApi;
Expand Down Expand Up @@ -53,8 +54,11 @@ describe("AuthApi", () => {
function loginFullyViaGithub(): void {
service.fetchUserInfoAndTokenFromGithubCallackCode(TEST_GITHUB_CODE).subscribe();

const op = controller.expectOne(GithubLoginDocument);
expect(op.operation.variables.code).toEqual(TEST_GITHUB_CODE);
const expectedCredentials: GithubLoginCredentials = { code: TEST_GITHUB_CODE };

const op = controller.expectOne(LoginDocument);
expect(op.operation.variables.login_method).toEqual(LOGIN_METHOD_GITHUB);
expect(op.operation.variables.login_credentials_json).toEqual(JSON.stringify(expectedCredentials));

op.flush({
data: mockGithubLoginResponse,
Expand All @@ -64,9 +68,11 @@ describe("AuthApi", () => {
function loginFullyViaPassword(): void {
service.fetchUserInfoAndTokenFromPasswordLogin(TEST_LOGIN, TEST_PASSWORD).subscribe();

const op = controller.expectOne(PasswordLoginDocument);
expect(op.operation.variables.login).toEqual(TEST_LOGIN);
expect(op.operation.variables.password).toEqual(TEST_PASSWORD);
const expectedCredentials: PasswordLoginCredentials = { login: TEST_LOGIN, password: TEST_PASSWORD };

const op = controller.expectOne(LoginDocument);
expect(op.operation.variables.login_method).toEqual(LOGIN_METHOD_PASSWORD);
expect(op.operation.variables.login_credentials_json).toEqual(JSON.stringify(expectedCredentials));

op.flush({
data: mockPasswordLoginResponse,
Expand All @@ -82,7 +88,7 @@ describe("AuthApi", () => {
.accessTokenObtained()
.pipe(first())
.subscribe((token: string) => {
expect(token).toEqual(mockPasswordLoginResponse.auth.passwordLogin.accessToken);
expect(token).toEqual(mockPasswordLoginResponse.auth.login.accessToken);
});

const accountChanged$ = service
Expand Down Expand Up @@ -111,10 +117,7 @@ describe("AuthApi", () => {
},
});

const op = controller.expectOne(PasswordLoginDocument);
expect(op.operation.variables.login).toEqual(TEST_LOGIN);
expect(op.operation.variables.password).toEqual(TEST_PASSWORD);

const op = controller.expectOne(LoginDocument);
op.graphqlErrors([mockLogin401Error]);
tick();

Expand All @@ -127,7 +130,7 @@ describe("AuthApi", () => {
.accessTokenObtained()
.pipe(first())
.subscribe((token: string) => {
expect(token).toEqual(mockGithubLoginResponse.auth.githubLogin.accessToken);
expect(token).toEqual(mockGithubLoginResponse.auth.login.accessToken);
});

const accountChanged$ = service
Expand Down Expand Up @@ -156,9 +159,7 @@ describe("AuthApi", () => {
},
});

const op = controller.expectOne(GithubLoginDocument);
expect(op.operation.variables.code).toEqual(TEST_GITHUB_CODE);

const op = controller.expectOne(LoginDocument);
op.graphqlErrors([mockLogin401Error]);
tick();

Expand Down
61 changes: 30 additions & 31 deletions src/app/api/auth.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ import {
AccountDetailsFragment,
FetchAccountInfoGQL,
FetchAccountInfoMutation,
GithubLoginGQL,
GithubLoginMutation,
PasswordLoginGQL,
PasswordLoginMutation,
LoginGQL,
LoginMutation,
} from "./kamu.graphql.interface";
import { MutationResult } from "apollo-angular";
import { AuthenticationError } from "../common/errors";
import {
GithubLoginCredentials,
LOGIN_METHOD_GITHUB,
LOGIN_METHOD_PASSWORD,
PasswordLoginCredentials,
} from "./auth.api.model";

@Injectable({
providedIn: "root",
})
export class AuthApi {
constructor(
private githubLoginGQL: GithubLoginGQL,
private passwordLoginGQL: PasswordLoginGQL,
private fetchAccountInfoGQL: FetchAccountInfoGQL,
) {}
constructor(private loginGQL: LoginGQL, private fetchAccountInfoGQL: FetchAccountInfoGQL) {}

private accessTokenObtained$: Subject<string> = new ReplaySubject<string>(1);
private accountChanged$: Subject<AccountDetailsFragment> = new ReplaySubject<AccountDetailsFragment>(1);
Expand All @@ -35,27 +35,21 @@ export class AuthApi {
}

public fetchUserInfoAndTokenFromPasswordLogin(login: string, password: string): Observable<void> {
return this.passwordLoginGQL.mutate({ login, password }).pipe(
map((result: MutationResult<PasswordLoginMutation>) => {
if (result.data) {
const data: PasswordLoginMutation = result.data;
this.accessTokenObtained$.next(data.auth.passwordLogin.accessToken);
this.accountChanged$.next(data.auth.passwordLogin.accountInfo);
} else {
throw new AuthenticationError(result.errors ?? []);
}
}),
catchError((e: Error) => throwError(() => new AuthenticationError([e]))),
);
const passwordCredentials: PasswordLoginCredentials = { login, password };
return this.fetchUserInfoAndTokenFromLoginMethod(LOGIN_METHOD_PASSWORD, passwordCredentials);
}

public fetchUserInfoAndTokenFromGithubCallackCode(code: string): Observable<void> {
return this.githubLoginGQL.mutate({ code }).pipe(
map((result: MutationResult<GithubLoginMutation>) => {
const githubCredentials: GithubLoginCredentials = { code };
return this.fetchUserInfoAndTokenFromLoginMethod(LOGIN_METHOD_GITHUB, githubCredentials);
}

public fetchUserInfoFromAccessToken(accessToken: string): Observable<void> {
return this.fetchAccountInfoGQL.mutate({ accessToken }).pipe(
map((result: MutationResult<FetchAccountInfoMutation>) => {
if (result.data) {
const data: GithubLoginMutation = result.data;
this.accessTokenObtained$.next(data.auth.githubLogin.accessToken);
this.accountChanged$.next(data.auth.githubLogin.accountInfo);
const data: FetchAccountInfoMutation = result.data;
this.accountChanged$.next(data.auth.accountInfo);
} else {
throw new AuthenticationError(result.errors ?? []);
}
Expand All @@ -64,12 +58,17 @@ export class AuthApi {
);
}

public fetchUserInfoFromAccessToken(accessToken: string): Observable<void> {
return this.fetchAccountInfoGQL.mutate({ accessToken }).pipe(
map((result: MutationResult<FetchAccountInfoMutation>) => {
private fetchUserInfoAndTokenFromLoginMethod<TCredentials>(
loginMethod: string,
loginCredentials: TCredentials,
): Observable<void> {
const loginCredentialsJson: string = JSON.stringify(loginCredentials);
return this.loginGQL.mutate({ login_method: loginMethod, login_credentials_json: loginCredentialsJson }).pipe(
map((result: MutationResult<LoginMutation>) => {
if (result.data) {
const data: FetchAccountInfoMutation = result.data;
this.accountChanged$.next(data.auth.accountInfo);
const data: LoginMutation = result.data;
this.accessTokenObtained$.next(data.auth.login.accessToken);
this.accountChanged$.next(data.auth.login.accountInfo);
} else {
throw new AuthenticationError(result.errors ?? []);
}
Expand Down
29 changes: 0 additions & 29 deletions src/app/api/gql/github-login.graphql

This file was deleted.

18 changes: 18 additions & 0 deletions src/app/api/gql/login.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mutation Login($login_method: String!, $login_credentials_json: String!) {
auth {
login(loginMethod: $login_method, loginCredentialsJson: $login_credentials_json) {
accessToken
accountInfo {
...AccountDetails
}
}
}
}

mutation FetchAccountInfo($accessToken: String!) {
auth {
accountInfo(accessToken: $accessToken) {
...AccountDetails
}
}
}
73 changes: 14 additions & 59 deletions src/app/api/kamu.graphql.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,16 @@ export type AttachmentsEmbedded = {
export type AuthMut = {
__typename?: "AuthMut";
accountInfo: AccountInfo;
githubLogin: LoginResponse;
passwordLogin: LoginResponse;
login: LoginResponse;
};

export type AuthMutAccountInfoArgs = {
accessToken: Scalars["String"];
};

export type AuthMutGithubLoginArgs = {
code: Scalars["String"];
};

export type AuthMutPasswordLoginArgs = {
login: Scalars["String"];
password: Scalars["String"];
export type AuthMutLoginArgs = {
loginCredentialsJson: Scalars["String"];
loginMethod: Scalars["String"];
};

export type BlockInterval = {
Expand Down Expand Up @@ -1747,32 +1742,16 @@ export type MetadataBlockFragment = {
| ({ __typename: "SetWatermark" } & SetWatermarkEventFragment);
};

export type GithubLoginMutationVariables = Exact<{
code: Scalars["String"];
export type LoginMutationVariables = Exact<{
login_method: Scalars["String"];
login_credentials_json: Scalars["String"];
}>;

export type GithubLoginMutation = {
export type LoginMutation = {
__typename?: "Mutation";
auth: {
__typename?: "AuthMut";
githubLogin: {
__typename?: "LoginResponse";
accessToken: string;
accountInfo: { __typename?: "AccountInfo" } & AccountDetailsFragment;
};
};
};

export type PasswordLoginMutationVariables = Exact<{
login: Scalars["String"];
password: Scalars["String"];
}>;

export type PasswordLoginMutation = {
__typename?: "Mutation";
auth: {
__typename?: "AuthMut";
passwordLogin: {
login: {
__typename?: "LoginResponse";
accessToken: string;
accountInfo: { __typename?: "AccountInfo" } & AccountDetailsFragment;
Expand Down Expand Up @@ -2799,34 +2778,10 @@ export class EnginesGQL extends Apollo.Query<EnginesQuery, EnginesQueryVariables
super(apollo);
}
}
export const GithubLoginDocument = gql`
mutation GithubLogin($code: String!) {
auth {
githubLogin(code: $code) {
accessToken
accountInfo {
...AccountDetails
}
}
}
}
${AccountDetailsFragmentDoc}
`;

@Injectable({
providedIn: "root",
})
export class GithubLoginGQL extends Apollo.Mutation<GithubLoginMutation, GithubLoginMutationVariables> {
document = GithubLoginDocument;

constructor(apollo: Apollo.Apollo) {
super(apollo);
}
}
export const PasswordLoginDocument = gql`
mutation PasswordLogin($login: String!, $password: String!) {
export const LoginDocument = gql`
mutation Login($login_method: String!, $login_credentials_json: String!) {
auth {
passwordLogin(login: $login, password: $password) {
login(loginMethod: $login_method, loginCredentialsJson: $login_credentials_json) {
accessToken
accountInfo {
...AccountDetails
Expand All @@ -2840,8 +2795,8 @@ export const PasswordLoginDocument = gql`
@Injectable({
providedIn: "root",
})
export class PasswordLoginGQL extends Apollo.Mutation<PasswordLoginMutation, PasswordLoginMutationVariables> {
document = PasswordLoginDocument;
export class LoginGQL extends Apollo.Mutation<LoginMutation, LoginMutationVariables> {
document = LoginDocument;

constructor(apollo: Apollo.Apollo) {
super(apollo);
Expand Down
Loading

0 comments on commit 39503a7

Please sign in to comment.