From 37beb7d9b6e221ea4cc6ae55e696473cdb599030 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 8 Oct 2020 20:07:51 +0200 Subject: [PATCH 01/19] Addinig initial implementation of Azure AD authentication --- packages/auth/package.json | 1 + packages/auth/src/authClients/azureAd.ts | 27 ++++++++++++++++ packages/auth/src/authClients/index.ts | 7 ++++- .../generate/auth/providers/azureAd.js | 31 +++++++++++++++++++ yarn.lock | 22 ++++++++++++- 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 packages/auth/src/authClients/azureAd.ts create mode 100644 packages/cli/src/commands/generate/auth/providers/azureAd.js diff --git a/packages/auth/package.json b/packages/auth/package.json index 4eaebe306ee6..acbc80d0daab 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -16,6 +16,7 @@ "firebase-admin": "^9.1.1", "gotrue-js": "git://github.com/netlify/gotrue-js.git#28df09cfcac505feadcb1719714d2a9507cf68eb", "magic-sdk": "^2.5.0", + "msal": "^1.4.1", "netlify-identity-widget": "1.9.1", "react": "^16.13.1" }, diff --git a/packages/auth/src/authClients/azureAd.ts b/packages/auth/src/authClients/azureAd.ts new file mode 100644 index 000000000000..a0978208d0ee --- /dev/null +++ b/packages/auth/src/authClients/azureAd.ts @@ -0,0 +1,27 @@ +import { UserAgentApplication } from 'msal' + +export type AzureAdClient = typeof UserAgentApplication + +import { AuthClient } from './' + +export const azureAd = (client: AzureAdClient): AuthClient => { + return { + type: 'azureAd', + client, + restoreAuthState: async () => {}, + login: async (options?) => { + await client.loginPopup(options) + }, + logout: (options?) => client.logout(options), + signup: async (options?) => { + await client.loginPopup(options) + }, + getToken: async () => { + return sessionStorage.getItem('msal.idtoken') + }, + getUserMetadata: async () => { + const user = await client.getAccount() + return user || null + }, + } +} diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index e7d77c18eaed..efeb073747b9 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -1,5 +1,6 @@ import type { NetlifyIdentity } from './netlify' import type { Auth0, Auth0User } from './auth0' +import type { AzureAd, AzureAdUser } from './azureAd' import type { GoTrue, GoTrueUser } from './goTrue' import type { MagicLink, MagicUser } from './magicLink' import type { Firebase } from './firebase' @@ -8,6 +9,7 @@ import type { Custom } from './custom' // import { netlify } from './netlify' import { auth0 } from './auth0' +import { azureAd } from './azureAd' import { goTrue } from './goTrue' import { magicLink } from './magicLink' import { firebase } from './firebase' @@ -17,6 +19,7 @@ import { custom } from './custom' const typesToClients = { netlify, auth0, + azureAd, goTrue, magicLink, firebase, @@ -27,6 +30,7 @@ const typesToClients = { export type SupportedAuthClients = | Auth0 + | AzureAd | GoTrue | NetlifyIdentity | MagicLink @@ -37,10 +41,11 @@ export type SupportedAuthClients = export type SupportedAuthTypes = keyof typeof typesToClients export type { Auth0User } +export type { AzureAdUser } export type { GoTrueUser } export type { MagicUser } export type { SupabaseUser } -export type SupportedUserMetadata = Auth0User | GoTrueUser | MagicUser | SupabaseUser +export type SupportedUserMetadata = Auth0User | AzureAdUser | GoTrueUser | MagicUser | SupabaseUser export interface AuthClient { restoreAuthState?(): void | Promise diff --git a/packages/cli/src/commands/generate/auth/providers/azureAd.js b/packages/cli/src/commands/generate/auth/providers/azureAd.js new file mode 100644 index 000000000000..82845a27b125 --- /dev/null +++ b/packages/cli/src/commands/generate/auth/providers/azureAd.js @@ -0,0 +1,31 @@ +// the lines that need to be added to index.js +export const config = { + imports: [`import { UserAgentApplication } from 'msal'`], + init: `const azureAdClient = new UserAgentApplication({ + auth: { + clientId: process.env.AZUREAD_CLIENT_ID, + authority: process.env.AZUREAD_AUTHORITY, + redirectUri: process.env.AZUREAD_REDIRECT_URI, + postLogoutRedirectUri: process.env.AZUREAD_LOGOUT_REDIRECT_URI, + }, + })`, + authProvider: { + client: 'azureAdClient', + type: 'azureAd', + }, +} + +// required packages to install +export const webPackages = ['msal'] +export const apiPackages = [] + +// any notes to print out when the job is done +export const notes = [ + 'You will need to create several environment variables with your Azure AD config options. Check out web/src/index.js for the variables you need to add.', + '\n', + 'RedwoodJS specific Documentation:', + 'https://redwoodjs.com/docs/authentication#azure-ad', + '\n', + 'MSAL.js Documentation:', + 'https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-js-initializing-client-applications' +] diff --git a/yarn.lock b/yarn.lock index 7e1909784940..53656d2b3107 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4363,7 +4363,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@16.9.38", "@types/react@16.9.49": +"@types/react@*", "@types/react@16.9.38": version "16.9.38" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.38.tgz#868405dace93a4095d3e054f4c4a1de7a1ac0680" integrity sha512-pHAeZbjjNRa/hxyNuLrvbxhhnKyKNiLC6I5fRF2Zr/t/S6zS41MiyzH4+c+1I9vVfvuRt1VS2Lodjr4ZWnxrdA== @@ -4371,6 +4371,14 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@16.9.49": + version "16.9.49" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872" + integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/require-dir@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/require-dir/-/require-dir-1.0.1.tgz#ea0b071a1c0f6dfa39674db7003cf51a0b70518a" @@ -7741,6 +7749,11 @@ csstype@^2.2.0, csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== +csstype@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" + integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -13593,6 +13606,13 @@ ms@2.1.2, ms@^2.0.0, ms@^2.1.1, ms@^2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +msal@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/msal/-/msal-1.4.1.tgz#06dfad9e413ccc7ceaaf3b4378814e00425e7c2a" + integrity sha512-FnRHbE7U7MoG5BAupupRP/0Zurvzc9OnZFiH6n9YeM9anRPYhur/gX87Gxw+DpM46kSoLHyKNtEN3U46fQw/8w== + dependencies: + tslib "^1.9.3" + msw@0.21.2, msw@^0.21.2: version "0.21.2" resolved "https://registry.yarnpkg.com/msw/-/msw-0.21.2.tgz#74ed10b8eb224325652a3c3812b5460dac297bd8" From 31f34c052fd09e7af6bf64927bda837ca258fe14 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 8 Oct 2020 21:30:18 +0200 Subject: [PATCH 02/19] Module has no exported member --- packages/auth/src/authClients/azureAd.ts | 7 ++++--- packages/auth/src/authClients/index.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/auth/src/authClients/azureAd.ts b/packages/auth/src/authClients/azureAd.ts index a0978208d0ee..23defaa80d57 100644 --- a/packages/auth/src/authClients/azureAd.ts +++ b/packages/auth/src/authClients/azureAd.ts @@ -1,8 +1,9 @@ -import { UserAgentApplication } from 'msal' +import { UserAgentApplication as AzureAd } from 'msal' -export type AzureAdClient = typeof UserAgentApplication +export type { AzureAd } +import type { AuthClient } from './' -import { AuthClient } from './' +export type AzureAdClient = AzureAd export const azureAd = (client: AzureAdClient): AuthClient => { return { diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index efeb073747b9..f7bb02f73184 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -1,6 +1,6 @@ import type { NetlifyIdentity } from './netlify' import type { Auth0, Auth0User } from './auth0' -import type { AzureAd, AzureAdUser } from './azureAd' +import type { AzureAd } from './azureAd' import type { GoTrue, GoTrueUser } from './goTrue' import type { MagicLink, MagicUser } from './magicLink' import type { Firebase } from './firebase' From 61509e5228f073de3a69aa8ccc8de827a0d12a40 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 8 Oct 2020 21:36:19 +0200 Subject: [PATCH 03/19] Exporting empty object for AzureAdUser --- packages/auth/src/authClients/azureAd.ts | 1 + packages/auth/src/authClients/index.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/auth/src/authClients/azureAd.ts b/packages/auth/src/authClients/azureAd.ts index 23defaa80d57..786c69b21753 100644 --- a/packages/auth/src/authClients/azureAd.ts +++ b/packages/auth/src/authClients/azureAd.ts @@ -4,6 +4,7 @@ export type { AzureAd } import type { AuthClient } from './' export type AzureAdClient = AzureAd +export interface AzureAdUser {} export const azureAd = (client: AzureAdClient): AuthClient => { return { diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index f7bb02f73184..efeb073747b9 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -1,6 +1,6 @@ import type { NetlifyIdentity } from './netlify' import type { Auth0, Auth0User } from './auth0' -import type { AzureAd } from './azureAd' +import type { AzureAd, AzureAdUser } from './azureAd' import type { GoTrue, GoTrueUser } from './goTrue' import type { MagicLink, MagicUser } from './magicLink' import type { Firebase } from './firebase' From f35c2a0676b324b29ab2ee81127cf3b1f7c7d447 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 8 Oct 2020 21:42:15 +0200 Subject: [PATCH 04/19] Adding azureAd to decoders with noop --- packages/api/src/auth/decoders/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/auth/decoders/index.ts b/packages/api/src/auth/decoders/index.ts index 4198c1b6b0f3..00c9e0668fac 100644 --- a/packages/api/src/auth/decoders/index.ts +++ b/packages/api/src/auth/decoders/index.ts @@ -8,6 +8,7 @@ const noop = (token: string) => token const typesToDecoders: Record = { auth0: auth0, + azureAd: noop, netlify: netlify, goTrue: netlify, magicLink: noop, From fe5cd60fe2d30588fbcc7cd10d9813dccc40fe53 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 8 Oct 2020 22:16:43 +0200 Subject: [PATCH 05/19] Adding api jwt decoding and generator auth.js template --- packages/api/src/auth/decoders/azureAd.ts | 7 +++++ packages/api/src/auth/decoders/index.ts | 3 +- .../auth/templates/azureAd.auth.js.template | 28 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 packages/api/src/auth/decoders/azureAd.ts create mode 100644 packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template diff --git a/packages/api/src/auth/decoders/azureAd.ts b/packages/api/src/auth/decoders/azureAd.ts new file mode 100644 index 000000000000..7209090d8d73 --- /dev/null +++ b/packages/api/src/auth/decoders/azureAd.ts @@ -0,0 +1,7 @@ +import jwt from 'jsonwebtoken' + +export const azureAd = (token: string) => { + // @todo: JWT validation + const decoded = jwt.decode(token) + return decoded +} diff --git a/packages/api/src/auth/decoders/index.ts b/packages/api/src/auth/decoders/index.ts index 00c9e0668fac..e468f2014c9b 100644 --- a/packages/api/src/auth/decoders/index.ts +++ b/packages/api/src/auth/decoders/index.ts @@ -4,11 +4,12 @@ import type { SupportedAuthTypes } from '@redwoodjs/auth' import { netlify } from './netlify' import { auth0 } from './auth0' +import { azureAd } from './azureAd' const noop = (token: string) => token const typesToDecoders: Record = { auth0: auth0, - azureAd: noop, + azureAd: azureAd, netlify: netlify, goTrue: netlify, magicLink: noop, diff --git a/packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template b/packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template new file mode 100644 index 000000000000..e57b3695fd6f --- /dev/null +++ b/packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template @@ -0,0 +1,28 @@ +// Define what you want `currentUser` to return throughout your app. For example, +// to return a real user from your database, you could do something like: +// +// export const getCurrentUser = async ({ email }) => { +// return await db.user.findOne({ where: { email } }) +// } + +import { AuthenticationError } from '@redwoodjs/api' + +export const getCurrentUser = async (decoded, { token, type }) => { + const { aud, iss, exp, name, sub, preferred_username: email } = decoded + + // Example: Use e-mail address + // const user = await db.user.findOne({ email: { email } })' + + // return user ?? {} + + return { email, name, sub, aud, iss, exp } +} + +// Use this function in your services to check that a user is logged in, and +// optionally raise an error if they're not. + +export const requireAuth = () => { + if (!context.currentUser) { + throw new AuthenticationError("You don't have permission to do that.") + } +} From 71aacd642616f71bac48d22a4274412fc0c2607d Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Sun, 18 Oct 2020 12:02:32 +0200 Subject: [PATCH 06/19] Renaming AzureAD references to AzureActiveDirectory --- packages/api/src/auth/decoders/azureAd.ts | 2 +- packages/api/src/auth/decoders/index.ts | 4 ++-- .../{azureAd.ts => azureActiveDirectory.ts} | 12 ++++++------ packages/auth/src/authClients/index.ts | 12 ++++++------ .../commands/generate/auth/providers/azureAd.js | 14 +++++++------- 5 files changed, 22 insertions(+), 22 deletions(-) rename packages/auth/src/authClients/{azureAd.ts => azureActiveDirectory.ts} (60%) diff --git a/packages/api/src/auth/decoders/azureAd.ts b/packages/api/src/auth/decoders/azureAd.ts index 7209090d8d73..5713536e8466 100644 --- a/packages/api/src/auth/decoders/azureAd.ts +++ b/packages/api/src/auth/decoders/azureAd.ts @@ -1,6 +1,6 @@ import jwt from 'jsonwebtoken' -export const azureAd = (token: string) => { +export const azureActiveDirectory = (token: string) => { // @todo: JWT validation const decoded = jwt.decode(token) return decoded diff --git a/packages/api/src/auth/decoders/index.ts b/packages/api/src/auth/decoders/index.ts index e468f2014c9b..9c570f980974 100644 --- a/packages/api/src/auth/decoders/index.ts +++ b/packages/api/src/auth/decoders/index.ts @@ -4,12 +4,12 @@ import type { SupportedAuthTypes } from '@redwoodjs/auth' import { netlify } from './netlify' import { auth0 } from './auth0' -import { azureAd } from './azureAd' +import { azureActiveDirectory } from './azureActiveDirectory' const noop = (token: string) => token const typesToDecoders: Record = { auth0: auth0, - azureAd: azureAd, + azureActiveDirectory: azureActiveDirectory, netlify: netlify, goTrue: netlify, magicLink: noop, diff --git a/packages/auth/src/authClients/azureAd.ts b/packages/auth/src/authClients/azureActiveDirectory.ts similarity index 60% rename from packages/auth/src/authClients/azureAd.ts rename to packages/auth/src/authClients/azureActiveDirectory.ts index 786c69b21753..2e605f53dd6e 100644 --- a/packages/auth/src/authClients/azureAd.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -1,14 +1,14 @@ -import { UserAgentApplication as AzureAd } from 'msal' +import { UserAgentApplication as AzureActiveDirectory } from 'msal' -export type { AzureAd } +export type { AzureActiveDirectory } import type { AuthClient } from './' -export type AzureAdClient = AzureAd -export interface AzureAdUser {} +export type AzureActiveDirectoryClient = AzureActiveDirectory +export interface AzureActiveDirectoryUser {} -export const azureAd = (client: AzureAdClient): AuthClient => { +export const azureActiveDirectory = (client: AzureActiveDirectoryClient): AuthClient => { return { - type: 'azureAd', + type: 'azureActiveDirectory', client, restoreAuthState: async () => {}, login: async (options?) => { diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index efeb073747b9..3212dee18def 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -1,6 +1,6 @@ import type { NetlifyIdentity } from './netlify' import type { Auth0, Auth0User } from './auth0' -import type { AzureAd, AzureAdUser } from './azureAd' +import type { AzureActiveDirectory, AzureActiveDirectoryUser } from './azureActiveDirectory' import type { GoTrue, GoTrueUser } from './goTrue' import type { MagicLink, MagicUser } from './magicLink' import type { Firebase } from './firebase' @@ -9,7 +9,7 @@ import type { Custom } from './custom' // import { netlify } from './netlify' import { auth0 } from './auth0' -import { azureAd } from './azureAd' +import { azureActiveDirectory } from './azureActiveDirectory' import { goTrue } from './goTrue' import { magicLink } from './magicLink' import { firebase } from './firebase' @@ -19,7 +19,7 @@ import { custom } from './custom' const typesToClients = { netlify, auth0, - azureAd, + azureActiveDirectory, goTrue, magicLink, firebase, @@ -30,7 +30,7 @@ const typesToClients = { export type SupportedAuthClients = | Auth0 - | AzureAd + | AzureActiveDirectory | GoTrue | NetlifyIdentity | MagicLink @@ -41,11 +41,11 @@ export type SupportedAuthClients = export type SupportedAuthTypes = keyof typeof typesToClients export type { Auth0User } -export type { AzureAdUser } +export type { AzureActiveDirectoryUser } export type { GoTrueUser } export type { MagicUser } export type { SupabaseUser } -export type SupportedUserMetadata = Auth0User | AzureAdUser | GoTrueUser | MagicUser | SupabaseUser +export type SupportedUserMetadata = Auth0User | AzureActiveDirectoryUser | GoTrueUser | MagicUser | SupabaseUser export interface AuthClient { restoreAuthState?(): void | Promise diff --git a/packages/cli/src/commands/generate/auth/providers/azureAd.js b/packages/cli/src/commands/generate/auth/providers/azureAd.js index 82845a27b125..381ecb07aa42 100644 --- a/packages/cli/src/commands/generate/auth/providers/azureAd.js +++ b/packages/cli/src/commands/generate/auth/providers/azureAd.js @@ -1,17 +1,17 @@ // the lines that need to be added to index.js export const config = { imports: [`import { UserAgentApplication } from 'msal'`], - init: `const azureAdClient = new UserAgentApplication({ + init: `const azureActiveDirectoryClient = new UserAgentApplication({ auth: { - clientId: process.env.AZUREAD_CLIENT_ID, - authority: process.env.AZUREAD_AUTHORITY, - redirectUri: process.env.AZUREAD_REDIRECT_URI, - postLogoutRedirectUri: process.env.AZUREAD_LOGOUT_REDIRECT_URI, + clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, + authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, + redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, + postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, }, })`, authProvider: { - client: 'azureAdClient', - type: 'azureAd', + client: 'azureActiveDirectoryClient', + type: 'azureActiveDirectory', }, } From 581d0d2ae234cb1688b7f6e5a651ede5a7995b3a Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Sat, 7 Nov 2020 11:54:47 +0100 Subject: [PATCH 07/19] Renaming decoder --- .../api/src/auth/decoders/{azureAd.ts => azureActiveDirectory.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/api/src/auth/decoders/{azureAd.ts => azureActiveDirectory.ts} (100%) diff --git a/packages/api/src/auth/decoders/azureAd.ts b/packages/api/src/auth/decoders/azureActiveDirectory.ts similarity index 100% rename from packages/api/src/auth/decoders/azureAd.ts rename to packages/api/src/auth/decoders/azureActiveDirectory.ts From ba10199713a709c9930ba7bcc04ec2f28d9ee83c Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Sun, 8 Nov 2020 18:02:21 +0100 Subject: [PATCH 08/19] Renaming generator and updating template --- .../{azureAd.js => azureActiveDirectory.js} | 0 .../azureActiveDirectory.auth.js.template | 61 +++++++++++++++++++ .../auth/templates/azureAd.auth.js.template | 28 --------- 3 files changed, 61 insertions(+), 28 deletions(-) rename packages/cli/src/commands/generate/auth/providers/{azureAd.js => azureActiveDirectory.js} (100%) create mode 100644 packages/cli/src/commands/generate/auth/templates/azureActiveDirectory.auth.js.template delete mode 100644 packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template diff --git a/packages/cli/src/commands/generate/auth/providers/azureAd.js b/packages/cli/src/commands/generate/auth/providers/azureActiveDirectory.js similarity index 100% rename from packages/cli/src/commands/generate/auth/providers/azureAd.js rename to packages/cli/src/commands/generate/auth/providers/azureActiveDirectory.js diff --git a/packages/cli/src/commands/generate/auth/templates/azureActiveDirectory.auth.js.template b/packages/cli/src/commands/generate/auth/templates/azureActiveDirectory.auth.js.template new file mode 100644 index 000000000000..ef7ced71b8f5 --- /dev/null +++ b/packages/cli/src/commands/generate/auth/templates/azureActiveDirectory.auth.js.template @@ -0,0 +1,61 @@ +// Define what you want `currentUser` to return throughout your app. For example, +// to return a real user from your database, you could do something like: +// +// export const getCurrentUser = async ({ email }) => { +// return await db.user.findOne({ where: { email } }) +// } + +import { AuthenticationError, ForbiddenError, parseJWT } from '@redwoodjs/api' + +export const getCurrentUser = async (decoded, { token, type }) => { + return { + email: decoded.preferred_username ?? null, + ...decoded, + roles: parseJWT({ decoded }).roles + } +} + +// Use this function in your services to check that a user is logged in, and +// optionally raise an error if they're not. + +/** + * Use requireAuth in your services to check that a user is logged in, + * whether or not they are assigned a role, and optionally raise an + * error if they're not. + * + * @param {string=} roles - An optional role or list of roles + * @param {string[]=} roles - An optional list of roles + + * @example + * + * // checks if currentUser is authenticated + * requireAuth() + * + * @example + * + * // checks if currentUser is authenticated and assigned one of the given roles + * requireAuth({ role: 'admin' }) + * requireAuth({ role: ['editor', 'author'] }) + * requireAuth({ role: ['publisher'] }) + */ +export const requireAuth = ({ role } = {}) => { + if (!context.currentUser) { + throw new AuthenticationError("You don't have permission to do that.") + } + + if ( + typeof role !== 'undefined' && + typeof role === 'string' && + !context.currentUser.roles?.includes(role) + ) { + throw new ForbiddenError("You don't have access to do that.") + } + + if ( + typeof role !== 'undefined' && + Array.isArray(role) && + !context.currentUser.roles?.some((r) => role.includes(r)) + ) { + throw new ForbiddenError("You don't have access to do that.") + } +} diff --git a/packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template b/packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template deleted file mode 100644 index e57b3695fd6f..000000000000 --- a/packages/cli/src/commands/generate/auth/templates/azureAd.auth.js.template +++ /dev/null @@ -1,28 +0,0 @@ -// Define what you want `currentUser` to return throughout your app. For example, -// to return a real user from your database, you could do something like: -// -// export const getCurrentUser = async ({ email }) => { -// return await db.user.findOne({ where: { email } }) -// } - -import { AuthenticationError } from '@redwoodjs/api' - -export const getCurrentUser = async (decoded, { token, type }) => { - const { aud, iss, exp, name, sub, preferred_username: email } = decoded - - // Example: Use e-mail address - // const user = await db.user.findOne({ email: { email } })' - - // return user ?? {} - - return { email, name, sub, aud, iss, exp } -} - -// Use this function in your services to check that a user is logged in, and -// optionally raise an error if they're not. - -export const requireAuth = () => { - if (!context.currentUser) { - throw new AuthenticationError("You don't have permission to do that.") - } -} From 9b80e7e5d34f9ecae9c002947488a94e6e3299cd Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Sun, 8 Nov 2020 18:15:38 +0100 Subject: [PATCH 09/19] Adding token verification based off Auth0 sample --- .../src/auth/decoders/azureActiveDirectory.ts | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/api/src/auth/decoders/azureActiveDirectory.ts b/packages/api/src/auth/decoders/azureActiveDirectory.ts index 5713536e8466..3a02addc0275 100644 --- a/packages/api/src/auth/decoders/azureActiveDirectory.ts +++ b/packages/api/src/auth/decoders/azureActiveDirectory.ts @@ -1,7 +1,49 @@ import jwt from 'jsonwebtoken' +import jwksClient from 'jwks-rsa' -export const azureActiveDirectory = (token: string) => { - // @todo: JWT validation - const decoded = jwt.decode(token) - return decoded +export const verifyAzureActiveDirectoryToken = ( + bearerToken: string +): Promise> => { + return new Promise((resolve, reject) => { + const { AZURE_ACTIVE_DIRECTORY_AUTHORITY } = process.env + if (!AZURE_ACTIVE_DIRECTORY_AUTHORITY) { + throw new Error( + '`AZURE_ACTIVE_DIRECTORY_AUTHORITY` env vars are not set.' + ) + } + + // @docs: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#sample-response + const client = jwksClient({ + jwksUri: `${AZURE_ACTIVE_DIRECTORY_AUTHORITY}/discovery/v2.0/keys`, + }) + + jwt.verify( + bearerToken, + (header, callback) => { + client.getSigningKey(header.kid as string, (error, key) => { + callback(error, key.getPublicKey()) + }) + }, + { + issuer: `${AZURE_ACTIVE_DIRECTORY_AUTHORITY}/v2.0`, + algorithms: ['RS256'], + }, + (verifyError, decoded) => { + if (verifyError) { + return reject(verifyError) + } + resolve( + typeof decoded === 'undefined' + ? null + : (decoded as Record) + ) + } + ) + }) +} + +export const azureActiveDirectory = async ( + token: string +): Promise> => { + return verifyAzureActiveDirectoryToken(token) } From a222300bc5282a2fabef17362ff9eefdd572f1b8 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 16:53:16 +0100 Subject: [PATCH 10/19] Updating text if env is not set Co-authored-by: Tobbe Lundberg --- packages/api/src/auth/decoders/azureActiveDirectory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/auth/decoders/azureActiveDirectory.ts b/packages/api/src/auth/decoders/azureActiveDirectory.ts index 3a02addc0275..b50113c9e0fa 100644 --- a/packages/api/src/auth/decoders/azureActiveDirectory.ts +++ b/packages/api/src/auth/decoders/azureActiveDirectory.ts @@ -8,7 +8,7 @@ export const verifyAzureActiveDirectoryToken = ( const { AZURE_ACTIVE_DIRECTORY_AUTHORITY } = process.env if (!AZURE_ACTIVE_DIRECTORY_AUTHORITY) { throw new Error( - '`AZURE_ACTIVE_DIRECTORY_AUTHORITY` env vars are not set.' + '`AZURE_ACTIVE_DIRECTORY_AUTHORITY` env var is not set.' ) } From 6d7044c9d1446aec3ac838e03760f97b35296976 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 16:54:51 +0100 Subject: [PATCH 11/19] Fixing docs comment formatting --- packages/api/src/auth/decoders/azureActiveDirectory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/auth/decoders/azureActiveDirectory.ts b/packages/api/src/auth/decoders/azureActiveDirectory.ts index b50113c9e0fa..e61dcbf97bfa 100644 --- a/packages/api/src/auth/decoders/azureActiveDirectory.ts +++ b/packages/api/src/auth/decoders/azureActiveDirectory.ts @@ -12,7 +12,7 @@ export const verifyAzureActiveDirectoryToken = ( ) } - // @docs: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#sample-response + /** @docs: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc#sample-response */ const client = jwksClient({ jwksUri: `${AZURE_ACTIVE_DIRECTORY_AUTHORITY}/discovery/v2.0/keys`, }) From 0a9d8e922ff37ca10dd620e82cb57606c6f41e56 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 16:57:33 +0100 Subject: [PATCH 12/19] No need to export verifyAzureActiveDirectoryToken --- packages/api/src/auth/decoders/azureActiveDirectory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/auth/decoders/azureActiveDirectory.ts b/packages/api/src/auth/decoders/azureActiveDirectory.ts index e61dcbf97bfa..cb2a4b313437 100644 --- a/packages/api/src/auth/decoders/azureActiveDirectory.ts +++ b/packages/api/src/auth/decoders/azureActiveDirectory.ts @@ -1,7 +1,7 @@ import jwt from 'jsonwebtoken' import jwksClient from 'jwks-rsa' -export const verifyAzureActiveDirectoryToken = ( +const verifyAzureActiveDirectoryToken = ( bearerToken: string ): Promise> => { return new Promise((resolve, reject) => { From 5b2caee1228e8c6579d0f202f691041188fe1720 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 16:58:26 +0100 Subject: [PATCH 13/19] One line Co-authored-by: Tobbe Lundberg --- packages/auth/src/authClients/azureActiveDirectory.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/auth/src/authClients/azureActiveDirectory.ts b/packages/auth/src/authClients/azureActiveDirectory.ts index 2e605f53dd6e..4e5d649524f4 100644 --- a/packages/auth/src/authClients/azureActiveDirectory.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -11,9 +11,7 @@ export const azureActiveDirectory = (client: AzureActiveDirectoryClient): AuthCl type: 'azureActiveDirectory', client, restoreAuthState: async () => {}, - login: async (options?) => { - await client.loginPopup(options) - }, + login: async (options?) => await client.loginPopup(options), logout: (options?) => client.logout(options), signup: async (options?) => { await client.loginPopup(options) From 5c651a3bb959d5b329a1b99800f3a6a2aa1d5d5d Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 16:58:40 +0100 Subject: [PATCH 14/19] One line Co-authored-by: Tobbe Lundberg --- packages/auth/src/authClients/azureActiveDirectory.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/auth/src/authClients/azureActiveDirectory.ts b/packages/auth/src/authClients/azureActiveDirectory.ts index 4e5d649524f4..abbea9b6ceaa 100644 --- a/packages/auth/src/authClients/azureActiveDirectory.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -16,9 +16,7 @@ export const azureActiveDirectory = (client: AzureActiveDirectoryClient): AuthCl signup: async (options?) => { await client.loginPopup(options) }, - getToken: async () => { - return sessionStorage.getItem('msal.idtoken') - }, + getToken: async () => sessionStorage.getItem('msal.idtoken'), getUserMetadata: async () => { const user = await client.getAccount() return user || null From a5c608ac87daf42b574db1a1bbbeeec4a6620056 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 17:00:27 +0100 Subject: [PATCH 15/19] One line Co-authored-by: Tobbe Lundberg --- packages/auth/src/authClients/azureActiveDirectory.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/auth/src/authClients/azureActiveDirectory.ts b/packages/auth/src/authClients/azureActiveDirectory.ts index abbea9b6ceaa..5ce350f2ad34 100644 --- a/packages/auth/src/authClients/azureActiveDirectory.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -17,9 +17,6 @@ export const azureActiveDirectory = (client: AzureActiveDirectoryClient): AuthCl await client.loginPopup(options) }, getToken: async () => sessionStorage.getItem('msal.idtoken'), - getUserMetadata: async () => { - const user = await client.getAccount() - return user || null - }, + getUserMetadata: async () => (await client.getAccount()) || null, } } From f14a58c31124662ac9df1e715d78cdeea5d14262 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 17:02:26 +0100 Subject: [PATCH 16/19] One line Co-authored-by: Tobbe Lundberg --- packages/auth/src/authClients/azureActiveDirectory.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/auth/src/authClients/azureActiveDirectory.ts b/packages/auth/src/authClients/azureActiveDirectory.ts index 5ce350f2ad34..b805045d8bfe 100644 --- a/packages/auth/src/authClients/azureActiveDirectory.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -13,9 +13,7 @@ export const azureActiveDirectory = (client: AzureActiveDirectoryClient): AuthCl restoreAuthState: async () => {}, login: async (options?) => await client.loginPopup(options), logout: (options?) => client.logout(options), - signup: async (options?) => { - await client.loginPopup(options) - }, + signup: async (options?) => await client.loginPopup(options), getToken: async () => sessionStorage.getItem('msal.idtoken'), getUserMetadata: async () => (await client.getAccount()) || null, } From d458839a1606ed5b174393bc73595ad0a8dac438 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 17:10:26 +0100 Subject: [PATCH 17/19] Removing restoreAuthState method No method that corresponds with this in MSAL's UserAgentApplication class https://pub.dev/documentation/msal_js/latest/msal_js/UserAgentApplication-class.html#constructors --- packages/auth/src/authClients/azureActiveDirectory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/auth/src/authClients/azureActiveDirectory.ts b/packages/auth/src/authClients/azureActiveDirectory.ts index b805045d8bfe..8d643f56d631 100644 --- a/packages/auth/src/authClients/azureActiveDirectory.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -10,7 +10,6 @@ export const azureActiveDirectory = (client: AzureActiveDirectoryClient): AuthCl return { type: 'azureActiveDirectory', client, - restoreAuthState: async () => {}, login: async (options?) => await client.loginPopup(options), logout: (options?) => client.logout(options), signup: async (options?) => await client.loginPopup(options), From 260a94538991744c3896713cf736f8d5944e96f1 Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 17:43:48 +0100 Subject: [PATCH 18/19] ESLint fix: There should be no empty line within import group --- packages/auth/src/authClients/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/auth/src/authClients/index.ts b/packages/auth/src/authClients/index.ts index 6c0ff83f5fba..b8e7ba6bde45 100644 --- a/packages/auth/src/authClients/index.ts +++ b/packages/auth/src/authClients/index.ts @@ -1,24 +1,17 @@ import type { Auth0, Auth0User } from './auth0' import { auth0 } from './auth0' - import type { AzureActiveDirectory, AzureActiveDirectoryUser } from './azureActiveDirectory' import { azureActiveDirectory } from './azureActiveDirectory' - import type { Custom } from './custom' import { custom } from './custom' - import type { Firebase } from './firebase' import { firebase } from './firebase' - import type { GoTrue, GoTrueUser } from './goTrue' import { goTrue } from './goTrue' - import type { MagicLink, MagicUser } from './magicLink' import { magicLink } from './magicLink' - import type { NetlifyIdentity } from './netlify' import { netlify } from './netlify' - import type { Supabase, SupabaseUser } from './supabase' import { supabase } from './supabase' From 086bd3e5dea7a8fc8102ce423cdf8df09efec70a Mon Sep 17 00:00:00 2001 From: Johan Eliasson Date: Thu, 3 Dec 2020 17:52:18 +0100 Subject: [PATCH 19/19] Updating auth's README with outdated SupportedAuthClients --- packages/auth/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/auth/README.md b/packages/auth/README.md index fc25bbc08fc2..59ee7142c16c 100644 --- a/packages/auth/README.md +++ b/packages/auth/README.md @@ -52,5 +52,13 @@ If you want to use the useAuth hook `Sign Up` with Auth0 to default the UI to th ```ts // authClients/index.ts -export type SupportedAuthClients = Auth0 | GoTrue | NetlifyIdentity | MagicLink +export type SupportedAuthClients = + | Auth0 + | AzureActiveDirectory + | GoTrue + | NetlifyIdentity + | MagicLink + | Firebase + | Supabase + | Custom ```