From ed35a87bf51e7c00c5d4fbf994653e72d34124d2 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 23 Feb 2024 09:16:36 +0100 Subject: [PATCH 1/4] Switch teams query to nais-api spec Co-authored-by: Frode Sundby Co-authored-by: Johnny Horvi --- src/nais-teams.ts | 48 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/nais-teams.ts b/src/nais-teams.ts index 12c7c53..01c5647 100644 --- a/src/nais-teams.ts +++ b/src/nais-teams.ts @@ -7,21 +7,31 @@ export type User = { name: string; email: string; teams: { - role: string; - team: { - slug: string; - }; - }[]; + nodes: { + role: string; + team: { + slug: string; + }; + }[]; + }; + pageInfo: { + hasNextPage: boolean; + }; }; export const lookupUserQuery = `query LookupUser($email: String!) { - userByEmail(email:$email) { - name, - email, - teams { - role, - team { - slug, + user(email: $email) { + name + email + teams(limit: 100) { + nodes { + role + team { + slug + } + } + pageInfo { + hasNextPage } } } @@ -91,6 +101,12 @@ export class NaisTeams { throw new Error(json.errors[0].message); } + if (json.data.pageInfo.hasNextPage) { + logger.error( + "lookupUser: user has more than 100 teams, pagination not implemented" + ); + } + logger.info("lookupUser: user found"); logger.debug("lookupUser: response", json); @@ -103,7 +119,7 @@ export class NaisTeams { * @returns A Promise that resolves to an object containing a status and user object. */ authorize = async ( - email: string, + email: string ): Promise<{ status: boolean; user: User | null }> => { const logger = getLogger("nais/nais-teams.ts"); @@ -129,7 +145,9 @@ export class NaisTeams { } logger.info("authorize: user found", userByEmail); - const { teams } = userByEmail; + const { + teams: { nodes: teams }, + } = userByEmail; if (!teams) { logger.warn("authorize: user has no teams", userByEmail); @@ -137,7 +155,7 @@ export class NaisTeams { } const allowedTeams = teams.filter((team: any) => - this.allowedTeams.includes(team.team.slug), + this.allowedTeams.includes(team.team.slug) ); logger.debug("authorize: allowed user teams", allowedTeams); From f6e477dcec4b14025519cd6a4dc99f3bc19507e0 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 23 Feb 2024 09:22:42 +0100 Subject: [PATCH 2/4] Fix linting issue --- src/nais-teams.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nais-teams.ts b/src/nais-teams.ts index 01c5647..74bdbf2 100644 --- a/src/nais-teams.ts +++ b/src/nais-teams.ts @@ -103,7 +103,7 @@ export class NaisTeams { if (json.data.pageInfo.hasNextPage) { logger.error( - "lookupUser: user has more than 100 teams, pagination not implemented" + "lookupUser: user has more than 100 teams, pagination not implemented", ); } @@ -119,7 +119,7 @@ export class NaisTeams { * @returns A Promise that resolves to an object containing a status and user object. */ authorize = async ( - email: string + email: string, ): Promise<{ status: boolean; user: User | null }> => { const logger = getLogger("nais/nais-teams.ts"); @@ -155,7 +155,7 @@ export class NaisTeams { } const allowedTeams = teams.filter((team: any) => - this.allowedTeams.includes(team.team.slug) + this.allowedTeams.includes(team.team.slug), ); logger.debug("authorize: allowed user teams", allowedTeams); From 845a86133aa472dddf468b3d7875d2f8b6c15edf Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 23 Feb 2024 09:50:34 +0100 Subject: [PATCH 3/4] Fix tests --- src/nais-teams.test.ts | 67 ++++++++++++++++++++++++------------------ src/nais-teams.ts | 18 ++++++------ src/server.test.ts | 46 +++++++++++++++++------------ 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/nais-teams.test.ts b/src/nais-teams.test.ts index 30702ae..7fd9d79 100644 --- a/src/nais-teams.test.ts +++ b/src/nais-teams.test.ts @@ -12,18 +12,23 @@ describe("NaisTeams", () => { const expectedUser: User = { name: "John Doe", email: "user@example.com", - teams: [ - { - role: "admin", - team: { - slug: "team-a", + teams: { + nodes: [ + { + role: "admin", + team: { + slug: "team-a", + }, }, + ], + pageInfo: { + hasNextPage: false, }, - ], + }, }; const mockResponse = { data: { - userByEmail: expectedUser, + user: expectedUser, }, }; jest.spyOn(global, "fetch").mockResolvedValueOnce({ @@ -52,7 +57,7 @@ describe("NaisTeams", () => { const email = "non-existent-user@example.com"; const mockResponse = { data: { - userByEmail: null, + user: null, }, }; jest.spyOn(global, "fetch").mockResolvedValueOnce({ @@ -91,7 +96,7 @@ describe("NaisTeams", () => { } as any); await expect(naisTeams.lookupUser(email)).rejects.toThrow( - mockResponse.errors[0].message, + mockResponse.errors[0].message ); }); }); @@ -99,21 +104,24 @@ describe("NaisTeams", () => { describe("authorize", () => { it("should return status true and user object when user is authorized", async () => { const email = "user@example.com"; - const expectedUser = { + const expectedUser: User = { name: "John Doe", email: "user@example.com", - teams: [ - { - role: "admin", - team: { - slug: "team-a", + teams: { + nodes: [ + { + role: "admin", + team: { + slug: "team-a", + }, }, - }, - ], + ], + pageInfo: { hasNextPage: false }, + }, }; const mockResponse = { data: { - userByEmail: expectedUser, + user: expectedUser, }, }; jest.spyOn(global, "fetch").mockResolvedValueOnce({ @@ -142,7 +150,7 @@ describe("NaisTeams", () => { const email = "non-existent-user@example.com"; const mockResponse = { data: { - userByEmail: null, + user: null, }, }; jest.spyOn(global, "fetch").mockResolvedValueOnce({ @@ -169,21 +177,24 @@ describe("NaisTeams", () => { it("should return status false and null user when user is not in allowed teams", async () => { const email = "user@example.com"; - const user = { + const user: User = { name: "John Doe", email: "user@example.com", - teams: [ - { - role: "admin", - team: { - slug: "team-c", + teams: { + nodes: [ + { + role: "admin", + team: { + slug: "team-c", + }, }, - }, - ], + ], + pageInfo: { hasNextPage: false }, + }, }; const mockResponse = { data: { - userByEmail: user, + user: user, }, }; jest.spyOn(global, "fetch").mockResolvedValueOnce({ diff --git a/src/nais-teams.ts b/src/nais-teams.ts index 74bdbf2..19c8701 100644 --- a/src/nais-teams.ts +++ b/src/nais-teams.ts @@ -13,9 +13,9 @@ export type User = { slug: string; }; }[]; - }; - pageInfo: { - hasNextPage: boolean; + pageInfo: { + hasNextPage: boolean; + }; }; }; @@ -94,23 +94,23 @@ export class NaisTeams { logger.debug("lookupUser: response status", response.status); logger.debug("lookupUser: response headers", response.headers); - const json: any = await response.json(); + const json: { data: { user: User }; errors: any[] } = await response.json(); if (json.errors) { logger.error("lookupUser: json errors", json.errors); throw new Error(json.errors[0].message); } - if (json.data.pageInfo.hasNextPage) { + if (json.data.user?.teams.pageInfo.hasNextPage) { logger.error( - "lookupUser: user has more than 100 teams, pagination not implemented", + "lookupUser: user has more than 100 teams, pagination not implemented" ); } logger.info("lookupUser: user found"); logger.debug("lookupUser: response", json); - return json.data.userByEmail; + return json.data.user; }; /** @@ -119,7 +119,7 @@ export class NaisTeams { * @returns A Promise that resolves to an object containing a status and user object. */ authorize = async ( - email: string, + email: string ): Promise<{ status: boolean; user: User | null }> => { const logger = getLogger("nais/nais-teams.ts"); @@ -155,7 +155,7 @@ export class NaisTeams { } const allowedTeams = teams.filter((team: any) => - this.allowedTeams.includes(team.team.slug), + this.allowedTeams.includes(team.team.slug) ); logger.debug("authorize: allowed user teams", allowedTeams); diff --git a/src/server.test.ts b/src/server.test.ts index 008768d..4b9b47c 100644 --- a/src/server.test.ts +++ b/src/server.test.ts @@ -1,12 +1,12 @@ +import { KeyObject } from "crypto"; +import { TeamsService, User } from "nais-teams"; +import nock from "nock"; +import request from "supertest"; import { IUnleash } from "unleash-server"; -import naisleash from "./server"; import Cache from "./cache"; -import request from "supertest"; +import { IAP_AUDIENCE, IAP_JWT_HEADER, IAP_JWT_ISSUER } from "./google-iap"; +import naisleash from "./server"; import { newSignedToken } from "./utils"; -import nock from "nock"; -import { KeyObject } from "crypto"; -import { IAP_JWT_HEADER, IAP_JWT_ISSUER, IAP_AUDIENCE } from "./google-iap"; -import { TeamsService, User } from "nais-teams"; let mockTeamsService: TeamsService; let server: IUnleash; @@ -130,14 +130,19 @@ describe("Unleash server", () => { const mockUser: User = { name: "test", email: "test@example.com", - teams: [ - { - role: "admin", - team: { - slug: "team", + teams: { + nodes: [ + { + role: "admin", + team: { + slug: "team", + }, }, + ], + pageInfo: { + hasNextPage: false, }, - ], + }, }; jest.spyOn(mockTeamsService, "authorize").mockResolvedValueOnce({ @@ -172,14 +177,19 @@ describe("Unleash server", () => { const mockUser: User = { name: "test", email: "test@example.com", - teams: [ - { - role: "member", - team: { - slug: "team", + teams: { + nodes: [ + { + role: "member", + team: { + slug: "team", + }, }, + ], + pageInfo: { + hasNextPage: false, }, - ], + }, }; jest.spyOn(mockTeamsService, "authorize").mockResolvedValueOnce({ From 915940b9753e6185244613557af78e22ae2446f7 Mon Sep 17 00:00:00 2001 From: Thomas Krampl Date: Fri, 23 Feb 2024 09:52:44 +0100 Subject: [PATCH 4/4] Fix linting --- src/nais-teams.test.ts | 2 +- src/nais-teams.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nais-teams.test.ts b/src/nais-teams.test.ts index 7fd9d79..b7f3fc4 100644 --- a/src/nais-teams.test.ts +++ b/src/nais-teams.test.ts @@ -96,7 +96,7 @@ describe("NaisTeams", () => { } as any); await expect(naisTeams.lookupUser(email)).rejects.toThrow( - mockResponse.errors[0].message + mockResponse.errors[0].message, ); }); }); diff --git a/src/nais-teams.ts b/src/nais-teams.ts index 19c8701..8d5401b 100644 --- a/src/nais-teams.ts +++ b/src/nais-teams.ts @@ -103,7 +103,7 @@ export class NaisTeams { if (json.data.user?.teams.pageInfo.hasNextPage) { logger.error( - "lookupUser: user has more than 100 teams, pagination not implemented" + "lookupUser: user has more than 100 teams, pagination not implemented", ); } @@ -119,7 +119,7 @@ export class NaisTeams { * @returns A Promise that resolves to an object containing a status and user object. */ authorize = async ( - email: string + email: string, ): Promise<{ status: boolean; user: User | null }> => { const logger = getLogger("nais/nais-teams.ts"); @@ -155,7 +155,7 @@ export class NaisTeams { } const allowedTeams = teams.filter((team: any) => - this.allowedTeams.includes(team.team.slug) + this.allowedTeams.includes(team.team.slug), ); logger.debug("authorize: allowed user teams", allowedTeams);