Skip to content
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
10 changes: 9 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ GIT_REPO_ADMIN_DATA="https://github.com/AVAnnotate/admin-data.git"
GIT_REPO_ORG="AVAnnotate"
GIT_ADMIN_REPO="admin"
PUBLIC_GIT_REPO_PROJECT_TEMPLATE="project-template"
PUBLIC_REDIRECT_URL="http://localhost:4321"
PUBLIC_REDIRECT_URL="http://localhost:4321"

# UTexas GitHub Enterprise Managed User (EMU) OAuth credentials
# Create an OAuth App inside the UTexas enterprise at:
# https://github.com/enterprises/utexas-internal
# Set the Authorization callback URL to: {PUBLIC_REDIRECT_URL}/git-enterprise
# Leave blank to hide the UTexas EID login button.
PUBLIC_UTEXAS_GITHUB_CLIENT_ID=
UTEXAS_GITHUB_CLIENT_SECRET=
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ You can also run a local environment without linking to Netlify by running `npm

Note that running the site locally still requires authentication via GitHub, and changes made to projects will be applied, so be sure to create testing projects for playing around with any new and untested features.

### UTexas GitHub Enterprise Managed User (EMU) login

A second "Sign in with UTexas EID" button can be shown on the sign-in page to let UTexas users authenticate via the [GitHub Enterprise Managed Users SSO](https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/configuring-authentication-for-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users) for the `utexas-internal` enterprise (<https://github.com/enterprises/utexas-internal>).

To enable this button you need a separate OAuth App registered inside the UTexas enterprise and two additional environment variables:

| Variable | Description |
| --- | --- |
| `PUBLIC_UTEXAS_GITHUB_CLIENT_ID` | Client ID of the OAuth App created inside the UTexas enterprise |
| `UTEXAS_GITHUB_CLIENT_SECRET` | Client secret for that OAuth App (server-side only) |

**Creating the OAuth App:**
1. Navigate to <https://github.com/enterprises/utexas-internal> (requires enterprise admin access).
2. Go to *Settings → OAuth Apps → New OAuth App*.
3. Set **Authorization callback URL** to `{PUBLIC_REDIRECT_URL}/git-enterprise` (e.g. `https://avannotate.netlify.app/git-enterprise`).
4. Copy the **Client ID** into `PUBLIC_UTEXAS_GITHUB_CLIENT_ID` and generate/copy a **Client secret** into `UTEXAS_GITHUB_CLIENT_SECRET`.

When `PUBLIC_UTEXAS_GITHUB_CLIENT_ID` is blank (the default), the UTexas EID button is hidden, so the sign-in page works exactly as before for non-UTexas deployments.

# Astro Basics

```sh
Expand Down
7 changes: 6 additions & 1 deletion src/components/SignInGithub/SignInGitHub.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
border: 1px solid black;
color: white !important;
background-color: #015f86;
width: 2o0px;
width: 200px;
}

/* UTexas burnt-orange accent for the Enterprise Managed User button */
.sign-in-anchor-utexas {
background-color: #bf5700 !important;
}

.authorize-anchor {
Expand Down
83 changes: 49 additions & 34 deletions src/components/SignInGithub/SignInGitHub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,41 @@ import type { Translations } from '@ty/Types.ts';
interface SignInGitHubProps {
i18n: Translations;
}
// href=

// GitHub logo SVG shared by all sign-in buttons.
const GitHubLogo = () => (
<svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
fill='currentColor'
className='mr-2'
viewBox='0 0 1792 1792'
>
<path d='M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z'></path>
</svg>
);

export const SignInGitHub = (props: SignInGitHubProps) => {
// Show the loading overlay whenever any sign-in link is clicked.
useEffect(() => {
const fireEvent = document.getElementById('sign-in');

if (fireEvent) {
fireEvent.addEventListener('click', () => {
const elOverlay = document.getElementById('page-loader-overlay');
const elSpinner = document.getElementById('page-loader-spinner');
const showLoader = () => {
const elOverlay = document.getElementById('page-loader-overlay');
const elSpinner = document.getElementById('page-loader-spinner');
if (elOverlay && elSpinner) {
elOverlay.classList.add('loading-state');
elSpinner.classList.add('loading');
}
};

if (elOverlay && elSpinner) {
elOverlay.classList.add('loading-state');
elSpinner.classList.add('loading');
}
});
}
['sign-in', 'sign-in-utexas'].forEach((id) => {
document.getElementById(id)?.addEventListener('click', showLoader);
});
}, []);

// Only show the UTexas EID button when the enterprise OAuth app is configured.
const utexasClientId = import.meta.env.PUBLIC_UTEXAS_GITHUB_CLIENT_ID;

return (
<div className='sign-in-container'>
<a
Expand All @@ -36,39 +53,37 @@ export const SignInGitHub = (props: SignInGitHubProps) => {
}/git&scope=repo%20workflow`}
>
<div className='sign-in-button-container'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
fill='currentColor'
className='mr-2'
viewBox='0 0 1792 1792'
>
<path d='M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z'></path>
</svg>
<GitHubLogo />
<div className='sign-in-button-text'>
{props.i18n.t['Sign in with GitHub']}
</div>
</div>
</a>
{utexasClientId && (
<a
id='sign-in-utexas'
className='sign-in-anchor sign-in-anchor-utexas'
href={`https://github.com/login/oauth/authorize?client_id=${utexasClientId}&redirect_uri=${
import.meta.env.PUBLIC_REDIRECT_URL
}/git-enterprise&scope=repo%20workflow`}
>
<div className='sign-in-button-container'>
<GitHubLogo />
<div className='sign-in-button-text'>
{props.i18n.t['Sign in with UTexas EID']}
</div>
</div>
</a>
)}
<a
id='sign-in'
id='sign-in-reauthorize'
className='authorize-anchor'
href={`https://github.com/settings/connections/applications/${
import.meta.env.PUBLIC_GITHUB_CLIENT_ID
}`}
>
<div className='sign-in-button-container'>
<svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
fill='currentColor'
className='mr-2'
viewBox='0 0 1792 1792'
>
<path d='M896 128q209 0 385.5 103t279.5 279.5 103 385.5q0 251-146.5 451.5t-378.5 277.5q-27 5-40-7t-13-30q0-3 .5-76.5t.5-134.5q0-97-52-142 57-6 102.5-18t94-39 81-66.5 53-105 20.5-150.5q0-119-79-206 37-91-8-204-28-9-81 11t-92 44l-38 24q-93-26-192-26t-192 26q-16-11-42.5-27t-83.5-38.5-85-13.5q-45 113-8 204-79 87-79 206 0 85 20.5 150t52.5 105 80.5 67 94 39 102.5 18q-39 36-49 103-21 10-45 15t-57 5-65.5-21.5-55.5-62.5q-19-32-48.5-52t-49.5-24l-20-3q-21 0-29 4.5t-5 11.5 9 14 13 12l7 5q22 10 43.5 38t31.5 51l10 23q13 38 44 61.5t67 30 69.5 7 55.5-3.5l23-4q0 38 .5 88.5t.5 54.5q0 18-13 30t-40 7q-232-77-378.5-277.5t-146.5-451.5q0-209 103-385.5t279.5-279.5 385.5-103zm-477 1103q3-7-7-12-10-3-13 2-3 7 7 12 9 6 13-2zm31 34q7-5-2-16-10-9-16-3-7 5 2 16 10 10 16 3zm30 45q9-7 0-19-8-13-17-6-9 5 0 18t17 7zm42 42q8-8-4-19-12-12-20-3-9 8 4 19 12 12 20 3zm57 25q3-11-13-16-15-4-19 7t13 15q15 6 19-6zm63 5q0-13-17-11-16 0-16 11 0 13 17 11 16 0 16-11zm58-10q-2-11-18-9-16 3-14 15t18 8 14-14z'></path>
</svg>
<GitHubLogo />
<div className='sign-in-button-text'>
{props.i18n.t['Reauthorize App and Organizations']}
</div>
Expand Down
1 change: 1 addition & 0 deletions src/i18n/en/sign-in.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"Sign in with GitHub": "Sign in with GitHub",
"Sign in with UTexas EID": "Sign in with UTexas EID",
"Welcome To": "Welcome To",
"Reauthorize App and Organizations": "Reauthorize App and Organizations"
}
49 changes: 49 additions & 0 deletions src/pages/git-enterprise/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { APIRoute } from 'astro';

// OAuth callback for UTexas GitHub Enterprise Managed User (EMU) login.
// GitHub redirects here after the user authorises the enterprise OAuth app at
// https://github.com/enterprises/utexas-internal.
// Required environment variables:
// PUBLIC_UTEXAS_GITHUB_CLIENT_ID – Client ID of the OAuth App registered
// inside the UTexas enterprise.
// UTEXAS_GITHUB_CLIENT_SECRET – Corresponding client secret (server-side
// only; never exposed to the browser).
export const GET: APIRoute = async ({ request }) => {
const code = new URL(request.url).searchParams.get('code');
const data = new FormData();
data.append('client_id', import.meta.env.PUBLIC_UTEXAS_GITHUB_CLIENT_ID);
data.append('client_secret', import.meta.env.UTEXAS_GITHUB_CLIENT_SECRET);
data.append('code', code ?? '');

// Exchange the authorisation code for an access token using the standard
// GitHub OAuth endpoint (same endpoint used for both regular and EMU apps).
return await fetch(`https://github.com/login/oauth/access_token`, {
method: 'POST',
body: data,
})
.then((response) => response.text())
.then((paramsString) => {
const params = new URLSearchParams(paramsString);
const access_token = params.get('access_token');
if (!access_token) {
console.error(
'UTexas OAuth token exchange failed: no access_token in response',
paramsString
);
return new Response(undefined, { status: 401 });
}
return new Response(undefined, {
status: 302,
headers: {
'Set-Cookie': `access-token=${access_token}; HttpOnly; SameSite=Lax; Path=/`,
Location: `${import.meta.env.PUBLIC_REDIRECT_URL}/en/projects`,
},
});
})
.catch((error) => {
console.error('UTexas OAuth token exchange failed:', error);
return new Response(undefined, {
status: 500,
});
});
};