Skip to content

Commit

Permalink
Merge pull request #196 from github-community-projects/sutterj/allowe…
Browse files Browse the repository at this point in the history
…d-orgs

feat: check for allowed orgs
  • Loading branch information
sutterj authored Jun 26, 2024
2 parents 9ac2972 + 0e498e0 commit 20a9b9d
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ NEXTAUTH_SECRET=bad-secret
NEXTAUTH_URL=http://localhost:3000
# A comma-separated list of GitHub usernames that are allowed to access the app
ALLOWED_HANDLES=
# A comma-separated list of GitHub orgs that are allowed to access the app
ALLOWED_ORGS=

# This is used to sign payloads from github, see this doc for more info
# https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
Expand Down
9 changes: 9 additions & 0 deletions env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export const env = createEnv({
if (val === '') return true
return val.split(',').every((handle) => handle.trim().length > 0)
}, 'Invalid comma separated list of GitHub handles'),
ALLOWED_ORGS: z
.string()
.optional()
.default('')
.refine((val) => {
if (val === '') return true
return val.split(',').every((org) => org.trim().length > 0)
}, 'Invalid comma separated list of GitHub orgs'),
},
/*
* Environment variables available on the client (and server).
Expand All @@ -57,6 +65,7 @@ export const env = createEnv({
PUBLIC_ORG: process.env.PUBLIC_ORG,
PRIVATE_ORG: process.env.PRIVATE_ORG,
ALLOWED_HANDLES: process.env.ALLOWED_HANDLES,
ALLOWED_ORGS: process.env.ALLOWED_ORGS,
},
skipValidation: process.env.SKIP_ENV_VALIDATIONS === 'true',
})
52 changes: 44 additions & 8 deletions src/app/api/auth/lib/nextauth-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,35 +131,71 @@ export const nextAuthOptions: AuthOptions = {
},
},
callbacks: {
signIn: (params) => {
signIn: async (params) => {
authLogger.debug('Sign in callback')

const profile = params.profile as Profile & { login?: string }

// If there is no login, prevent sign in
if (!profile?.login) {
return false
}

// Get the allowed handles list
const allowedHandles = (
process.env.ALLOWED_HANDLES?.split(',') ?? []
).filter((handle) => handle !== '')

if (allowedHandles.length === 0) {
// Get the allowed orgs list
const allowedOrgs = (process.env.ALLOWED_ORGS?.split(',') ?? []).filter(
(org) => org !== '',
)

// If there are no allowed handles and no allowed orgs specified, allow all users
if (allowedHandles.length === 0 && allowedOrgs.length === 0) {
authLogger.info(
'No allowed handles specified via ALLOWED_HANDLES, allowing all users.',
'No allowed handles or orgs specified, allowing all users.',
)
return true
}

if (!profile?.login) {
return false
return true
}

authLogger.debug('Trying to sign in with handle:', profile.login)

// If the user is in the allowed handles list, allow sign in
if (allowedHandles.includes(profile.login)) {
return true
}

authLogger.warn(
authLogger.debug(
`User "${profile.login}" is not in the allowed handles list`,
)

authLogger.debug(
"Checking if any of user's orgs are in allowed orgs list",
)

const octokit = personalOctokit(params.account?.access_token as string)

// Get the user's organizations
const orgs = await octokit
.paginate(octokit.rest.orgs.listForAuthenticatedUser)
.catch((error: Error) => {
authLogger.error('Failed to fetch organizations', { error })
return []
})

// Check if any of the user's organizations are in the allowed orgs list
if (orgs.some((org) => allowedOrgs.includes(org.login))) {
authLogger.info(
`User "${profile.login}" has an org in the allowed orgs list`,
)

return true
}

authLogger.warn(`User "${profile.login}" is not allowed to sign in`)

return false
},
session: ({ session, token }) => {
Expand Down

0 comments on commit 20a9b9d

Please sign in to comment.