Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AZURE_AD_ALLOWED_PRINCIPALS #192

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/5-add-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ We'll create two GitHub apps: one for testing locally and another for production
AZURE_AD_CLIENT_ID=
AZURE_AD_CLIENT_SECRET=
AZURE_AD_TENANT_ID=
AZURE_AD_ALLOWED_PRINCIPALS=
```

## Configure an admin user
Expand Down
1 change: 1 addition & 0 deletions docs/7-environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Below are the required environment variables, to be added to the Azure Portal or
| `AZURE_AD_CLIENT_ID` | | The client id specific to the application |
| `AZURE_AD_CLIENT_SECRET` | | The client secret specific to the application |
| `AZURE_AD_TENANT_ID` | | The organisation Tenant ID |
| `AZURE_AD_ALLOWED_PRINCIPALS` | | Comma separated list of Object IDs for users/groups that are allowed to log in. **All authenticated users are allowed by default** |
| `ADMIN_EMAIL_ADDRESS` | | Comma separated list of email addresses of the admin users ID |
| **Azure Cognitive Search is optional. This is only required for chat over file feature.** |
| `AZURE_SEARCH_API_KEY` | | API Key of Azure Cognitive search |
Expand Down
68 changes: 60 additions & 8 deletions src/features/auth/auth-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { hashValue } from "./helpers";
const configureIdentityProvider = () => {
const providers: Array<Provider> = [];

const adminEmails = process.env.ADMIN_EMAIL_ADDRESS?.split(",").map(email => email.toLowerCase().trim());
const adminEmails = process.env.ADMIN_EMAIL_ADDRESS?.split(",").map(email => email.toLowerCase().trim()).filter(email => email);
const azureAdAllowedPrincipals = process.env.AZURE_AD_ALLOWED_PRINCIPALS?.split(",").map(oid => oid.toLowerCase().trim()).filter(oid => oid);

if (process.env.AUTH_GITHUB_ID && process.env.AUTH_GITHUB_SECRET) {
providers.push(
Expand All @@ -18,7 +19,8 @@ const configureIdentityProvider = () => {
async profile(profile) {
const newProfile = {
...profile,
isAdmin: adminEmails?.includes(profile.email.toLowerCase())
isAdmin: adminEmails?.includes(profile.email.toLowerCase()),
isAllowed: true
}
return newProfile;
}
Expand All @@ -36,13 +38,60 @@ const configureIdentityProvider = () => {
clientId: process.env.AZURE_AD_CLIENT_ID!,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
tenantId: process.env.AZURE_AD_TENANT_ID!,
async profile(profile) {

authorization: {
params: {
// Add User.Read to reach the /me endpoint of Microsoft Graph
scope: 'email openid profile User.Read'
}
},
async profile(profile, tokens) {
let isAllowed = true
if (Array.isArray(azureAdAllowedPrincipals) && azureAdAllowedPrincipals.length > 0) {
try {
isAllowed = false
// POST https://graph.microsoft.com/v1.0/me/getMemberObjects
// It returns all IDs of principal objects which "me" is a member of (transitive)
// https://learn.microsoft.com/en-us/graph/api/directoryobject-getmemberobjects?view=graph-rest-1.0&tabs=http
const response = await fetch(
'https://graph.microsoft.com/v1.0/me/getMemberObjects',
{
method: 'POST',
headers: {
Authorization: `Bearer ${tokens.access_token}`,
'Content-Type': 'application/json'
},
body: '{"securityEnabledOnly":true}'
}
)
if (response.ok) {
const body = await response.json() as { value?: string[] }
const oids = body.value ?? []
if (profile.oid) {
// Append the object ID of user principal "me"
oids.push(profile.oid)
}
for (const principal of azureAdAllowedPrincipals) {
if (oids.includes(principal)) {
isAllowed = true
break
}
}
}
else {
const body = await response.text()
throw new Error(`Bad response from POST /me/getMemberObjects: ${response.status} ${response.statusText}: ${body}`)
}
}
catch (e) {
console.log(e)
}
}
const newProfile = {
...profile,
// throws error without this - unsure of the root cause (https://stackoverflow.com/questions/76244244/profile-id-is-missing-in-google-oauth-profile-response-nextauth)
id: profile.sub,
isAdmin: adminEmails?.includes(profile.email.toLowerCase()) || adminEmails?.includes(profile.preferred_username.toLowerCase())
isAdmin: adminEmails?.includes(profile.email.toLowerCase()) || adminEmails?.includes(profile.preferred_username.toLowerCase()),
isAllowed
}
return newProfile;
}
Expand Down Expand Up @@ -89,15 +138,18 @@ export const options: NextAuthOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [...configureIdentityProvider()],
callbacks: {
async jwt({token, user, account, profile, isNewUser, session}) {
async jwt({ token, user, account, profile, isNewUser, session }) {
if (user?.isAdmin) {
token.isAdmin = user.isAdmin
token.isAdmin = user.isAdmin
}
return token
},
async session({session, token, user }) {
async session({ session, token, user }) {
session.user.isAdmin = token.isAdmin as string
return session
},
async signIn({ user }) {
return user.isAllowed
}
},
session: {
Expand Down
1 change: 1 addition & 0 deletions src/types/next-auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ declare module "next-auth" {

interface User {
isAdmin: string
isAllowed: boolean
}

}