diff --git a/packages/brokeneck-fastify/lib/plugins/auth/azure/index.js b/packages/brokeneck-fastify/lib/plugins/auth/azure/index.js index a3c14c17..fd993f37 100644 --- a/packages/brokeneck-fastify/lib/plugins/auth/azure/index.js +++ b/packages/brokeneck-fastify/lib/plugins/auth/azure/index.js @@ -11,7 +11,7 @@ async function azure(fastify, options) { fastify.graphql.extendSchema(gql` extend type User { - objectId: ID! + id: ID! displayName: String! accountEnabled: Boolean } diff --git a/packages/brokeneck-fastify/lib/plugins/auth/azure/provider.js b/packages/brokeneck-fastify/lib/plugins/auth/azure/provider.js index cc7562ee..366df54b 100644 --- a/packages/brokeneck-fastify/lib/plugins/auth/azure/provider.js +++ b/packages/brokeneck-fastify/lib/plugins/auth/azure/provider.js @@ -1,35 +1,91 @@ 'use strict' +const { Client } = require('@microsoft/microsoft-graph-client') +const fetch = require('node-fetch') const { GraphRbacManagementClient } = require('@azure/graph') const { - listUsersNextOperationSpec, - listUsersOperationSpec, listGroupsOperationSpec, listGroupsNextOperationSpec, listGroupsUsersOperationSpec, listGroupsUsersNextOperationSpec } = require('./operations') +function ClientCredentialsProvider(options) { + let cachedToken + + function getStoredToken() { + if (cachedToken && cachedToken.expiry > Date.now()) { + return cachedToken.token + } + } + + function storeToken(token) { + cachedToken = { + token, + expiry: Date.now() + token.expires_in * 1000 + } + } + + return { + async getAccessToken() { + const storedToken = getStoredToken() + + if (storedToken) { + return storedToken.access_token + } + + const tokenRes = await fetch( + `https://login.microsoftonline.com/${options.tenantId}/oauth2/v2.0/token`, + { + method: 'POST', + body: new URLSearchParams({ + client_id: options.clientId, + client_secret: options.secret, + grant_type: 'client_credentials', + scope: 'https://graph.microsoft.com/.default' + }) + } + ) + + if (!tokenRes.ok) { + throw new Error('could not get an access token') + } + + const token = await tokenRes.json() + + storeToken(token) + + return token.access_token + } + } +} + function AzureProvider(options, credentials, logger) { + const authProvider = new ClientCredentialsProvider(options) const azure = new GraphRbacManagementClient(credentials, options.tenantId) + const client = Client.initWithMiddleware({ authProvider }) return { name: 'azure', async listUsers({ pageNumber, pageSize, search }) { - const options = { top: pageSize, search: `"displayName:${search}"` } + const api = client.api('/users').top(pageSize).count(true) - const result = await (pageNumber - ? azure.sendOperationRequest( - { - nextLink: pageNumber, - options - }, - listUsersNextOperationSpec - ) - : azure.sendOperationRequest({ options }, listUsersOperationSpec)) + if (search) api.search(`"displayName:${search}"`) + if (pageNumber) api.skipToken(pageNumber) - const users = { data: result, nextPage: result.odatanextLink } + const result = await api.get() + + const searchParams = + result['@odata.nextLink'] && + new URL(result['@odata.nextLink']).searchParams + + const users = { + data: result.value, + nextPage: searchParams + ? searchParams.get('$skiptoken') || searchParams.get('$skipToken') + : null + } logger.debug({ users }, 'loaded users') diff --git a/packages/brokeneck-fastify/package.json b/packages/brokeneck-fastify/package.json index 1da934ea..db87a6eb 100644 --- a/packages/brokeneck-fastify/package.json +++ b/packages/brokeneck-fastify/package.json @@ -18,6 +18,7 @@ "dependencies": { "@azure/graph": "^5.0.1", "@azure/ms-rest-nodeauth": "^3.0.6", + "@microsoft/microsoft-graph-client": "^2.2.1", "@nearform/brokeneck-html": "^1.0.0-spinal.3", "auth0": "^2.30.0", "aws-sdk": "^2.793.0", @@ -33,6 +34,7 @@ "graphql-tag": "^2.11.0", "graphql-tools": "^7.0.1", "mercurius": "^6.4.0", + "node-fetch": "^2.6.1", "pkg-dir": "^5.0.0", "point-of-view": "^4.7.0" }, diff --git a/yarn.lock b/yarn.lock index d2c606b3..560a18a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2770,6 +2770,15 @@ prop-types "^15.7.2" react-is "^16.8.0 || ^17.0.0" +"@microsoft/microsoft-graph-client@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@microsoft/microsoft-graph-client/-/microsoft-graph-client-2.2.1.tgz#0ef045e1210551f234466a234bb0c60ea2ad8334" + integrity sha512-fbDN3UJ+jtSP9llAejqmslMcv498YuIrS3OS/Luivb8OSjdUESZKdP0gcUunnuNIayePVT0/bGYSJTzAIptJQQ== + dependencies: + "@babel/runtime" "^7.4.4" + msal "^1.4.4" + tslib "^1.9.3" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -12184,6 +12193,13 @@ ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msal@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/msal/-/msal-1.4.4.tgz#3f9b5a4442aa711c12ab8e88b8ed89b293f99711" + integrity sha512-aOBD/L6jAsizDFzKxxvXxH0FEDjp6Inr3Ufi/Y2o7KCFKN+akoE2sLeszEb/0Y3VxHxK0F0ea7xQ/HHTomKivw== + dependencies: + tslib "^1.9.3" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"