Skip to content

Commit

Permalink
Combine auth properties into GALASA_TOKEN and remove client secret (#30)
Browse files Browse the repository at this point in the history
Signed-off-by: Eamonn Mansour <[email protected]>
  • Loading branch information
eamansour authored Feb 5, 2024
1 parent d216c5f commit 7a7515a
Show file tree
Hide file tree
Showing 10 changed files with 29 additions and 54 deletions.
1 change: 0 additions & 1 deletion galasa-ui/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@
GALASA_API_SERVER_URL="http://localhost:8080"
GALASA_WEBUI_HOST_URL="http://localhost:3000"
GALASA_WEBUI_CLIENT_ID="galasa-webui"
GALASA_WEBUI_CLIENT_SECRET="example-webui-client-secret"
6 changes: 2 additions & 4 deletions galasa-ui/src/app/auth/tokens/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ export async function POST() {
const dexClient = await getAuthApiClientWithAuthHeader().postClients();

const clientId = dexClient.clientId;
const clientSecret = dexClient.clientSecret;
if (clientId && clientSecret) {
// Store the client ID and secret to be displayed to the user later
if (clientId) {
// Store the client ID to be displayed to the user later
cookies().set(AuthCookies.CLIENT_ID, clientId, { httpOnly: true });
cookies().set(AuthCookies.CLIENT_SECRET, Buffer.from(clientSecret).toString('base64'), { httpOnly: true });

// Authenticate with the created client to get a new refresh token for this client
const authResponse = await sendAuthRequest(clientId);
Expand Down
4 changes: 1 addition & 3 deletions galasa-ui/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ import { cookies } from 'next/headers';
export default function HomePage() {

const clientId = cookies().get(AuthCookies.CLIENT_ID)?.value ?? '';
const clientSecret = cookies().get(AuthCookies.CLIENT_SECRET)?.value ?? '';
const refreshToken = cookies().get(AuthCookies.REFRESH_TOKEN)?.value ?? '';

// Server Action to delete auth-related cookies
const deleteCookies = async () => {
'use server';

cookies().delete(AuthCookies.CLIENT_ID);
cookies().delete(AuthCookies.CLIENT_SECRET);
cookies().delete(AuthCookies.REFRESH_TOKEN);
};

return (
<div id="content">
<TokenRequestModal />
<TokenResponseModal refreshToken={refreshToken} clientId={clientId} clientSecret={clientSecret} onLoad={deleteCookies} />
<TokenResponseModal refreshToken={refreshToken} clientId={clientId} onLoad={deleteCookies} />
</div>
);
};
17 changes: 5 additions & 12 deletions galasa-ui/src/components/TokenResponseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,23 @@ import { useEffect, useState } from 'react';
interface TokenResponseModalProps {
refreshToken: string;
clientId: string;
clientSecret: string;
onLoad: () => Promise<void>;
}

export default function TokenResponseModal({ refreshToken, clientId, clientSecret, onLoad }: TokenResponseModalProps) {
export default function TokenResponseModal({ refreshToken, clientId, onLoad }: TokenResponseModalProps) {
const [token, setToken] = useState('');
const [clientIdState, setClientId] = useState('');
const [secret, setSecret] = useState('');
const [isOpen, setOpen] = useState(false);

useEffect(() => {
if (refreshToken.length > 0 && clientId.length > 0 && clientSecret.length > 0) {
if (refreshToken.length > 0 && clientId.length > 0) {
setToken(refreshToken);
setClientId(clientId);
setSecret(clientSecret);
setOpen(true);

onLoad().catch((err) => console.error('Failed to load token response dialog: %s', err));
}
}, [clientId, clientSecret, refreshToken, onLoad]);
}, [clientId, refreshToken, onLoad]);

return (
<Modal
Expand All @@ -45,14 +42,10 @@ export default function TokenResponseModal({ refreshToken, clientId, clientSecre
}}
>
<p>
Copy the following properties into the galasactl.properties file in your Galasa home directory* or set them as environment variables in your
Copy the following property into the galasactl.properties file in your Galasa home directory* or set it as an environment variable in your
terminal to allow your client tool to access the Galasa Ecosystem.
</p>
<CodeSnippet type="multi">
{`GALASA_ACCESS_TOKEN=${token}
GALASA_CLIENT_ID=${clientIdState}
GALASA_SECRET=${secret}`}
</CodeSnippet>
<CodeSnippet type="multi">{`GALASA_TOKEN=${token}:${clientIdState}`}</CodeSnippet>
<InlineNotification
title="The personal access token details are not stored and cannot be retrieved when this dialog is closed."
subtitle="Remember to copy the details shown above before closing this dialog."
Expand Down
15 changes: 5 additions & 10 deletions galasa-ui/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,38 +80,33 @@ const handleCallback = async (request: NextRequest, response: NextResponse) => {

if (code) {
let clientId = '';
let clientSecret = '';
const clientIdCookie = request.cookies.get(AuthCookies.CLIENT_ID);
const clientSecretCookie = request.cookies.get(AuthCookies.CLIENT_SECRET);

if (!clientIdCookie || !clientSecretCookie) {
if (!clientIdCookie) {
clientId = process.env.GALASA_WEBUI_CLIENT_ID ?? '';
clientSecret = Buffer.from(process.env.GALASA_WEBUI_CLIENT_SECRET ?? '').toString('base64');
} else {
clientId = clientIdCookie.value;
clientSecret = clientSecretCookie.value;
}

// Build the request body
const authProperties = buildAuthProperties(clientId, clientSecret, code);
const authProperties = buildAuthProperties(clientId, code);

// Send a POST request to the API server's /auth endpoint to exchange the authorization code with a JWT
const tokenResponse = await authApiClient.postAuthenticate(authProperties);

if (tokenResponse.jwt && (!clientIdCookie || !clientSecretCookie)) {
if (tokenResponse.jwt && !clientIdCookie) {
response.cookies.set(AuthCookies.ID_TOKEN, tokenResponse.jwt, { httpOnly: true });
} else if (tokenResponse.refreshToken && clientIdCookie && clientSecretCookie) {
} else if (tokenResponse.refreshToken && clientIdCookie) {
response.cookies.set(AuthCookies.REFRESH_TOKEN, tokenResponse.refreshToken, { httpOnly: true });
}
}
return response;
};

const buildAuthProperties = (clientId: string, clientSecret: string, code: string) => {
const buildAuthProperties = (clientId: string, code: string) => {
const authProperties = new AuthProperties();

authProperties.clientId = clientId;
authProperties.secret = clientSecret;
authProperties.code = code;

return authProperties;
Expand Down
12 changes: 4 additions & 8 deletions galasa-ui/src/tests/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ exports[`renders Galasa Ecosystem homepage 1`] = `
id="cds--modal-body--modal-2"
>
<p>
Copy the following properties into the galasactl.properties file in your Galasa home directory* or set them as environment variables in your terminal to allow your client tool to access the Galasa Ecosystem.
Copy the following property into the galasactl.properties file in your Galasa home directory* or set it as an environment variable in your terminal to allow your client tool to access the Galasa Ecosystem.
</p>
<div
class="cds--snippet cds--snippet--multi"
Expand All @@ -212,9 +212,7 @@ exports[`renders Galasa Ecosystem homepage 1`] = `
>
<pre>
<code>
GALASA_ACCESS_TOKEN=
GALASA_CLIENT_ID=
GALASA_SECRET=
GALASA_TOKEN=:
</code>
</pre>
</div>
Expand Down Expand Up @@ -572,7 +570,7 @@ GALASA_SECRET=
id="cds--modal-body--modal-2"
>
<p>
Copy the following properties into the galasactl.properties file in your Galasa home directory* or set them as environment variables in your terminal to allow your client tool to access the Galasa Ecosystem.
Copy the following property into the galasactl.properties file in your Galasa home directory* or set it as an environment variable in your terminal to allow your client tool to access the Galasa Ecosystem.
</p>
<div
class="cds--snippet cds--snippet--multi"
Expand All @@ -588,9 +586,7 @@ GALASA_SECRET=
>
<pre>
<code>
GALASA_ACCESS_TOKEN=
GALASA_CLIENT_ID=
GALASA_SECRET=
GALASA_TOKEN=:
</code>
</pre>
</div>
Expand Down
14 changes: 7 additions & 7 deletions galasa-ui/src/tests/components/TokenResponseModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('Token response modal', () => {
it('renders invisible token response modal if all properties are empty', async () => {
// Given...
await act(async () => {
return render(<TokenResponseModal refreshToken="" clientId="" clientSecret="" onLoad={async () => {}} />);
return render(<TokenResponseModal refreshToken="" clientId="" onLoad={async () => {}} />);
});
const responseModalElement = screen.getByRole('presentation');

Expand All @@ -19,10 +19,10 @@ describe('Token response modal', () => {
expect(responseModalElement).not.toHaveClass('is-visible');
});

it('renders invisible token response modal if the clientSecret property is empty', async () => {
it('renders invisible token response modal if the clientId property is empty', async () => {
// Given...
await act(async () => {
return render(<TokenResponseModal refreshToken="dummytoken" clientId="dummyid" clientSecret="" onLoad={async () => {}} />);
return render(<TokenResponseModal refreshToken="dummytoken" clientId="" onLoad={async () => {}} />);
});
const responseModalElement = screen.getByRole('presentation');

Expand All @@ -31,10 +31,10 @@ describe('Token response modal', () => {
expect(responseModalElement).not.toHaveClass('is-visible');
});

it('renders invisible token response modal if the clientId property is empty', async () => {
it('renders invisible token response modal if the refreshToken property is empty', async () => {
// Given...
await act(async () => {
return render(<TokenResponseModal refreshToken="dummytoken" clientId="" clientSecret="dummysecret" onLoad={async () => {}} />);
return render(<TokenResponseModal refreshToken="" clientId="clientId" onLoad={async () => {}} />);
});
const responseModalElement = screen.getByRole('presentation');

Expand All @@ -46,7 +46,7 @@ describe('Token response modal', () => {
it('becomes visible when all required properties are provided', async () => {
// Given...
await act(async () => {
return render(<TokenResponseModal refreshToken="dummytoken" clientId="dummyid" clientSecret="dummysecret" onLoad={async () => {}} />);
return render(<TokenResponseModal refreshToken="dummytoken" clientId="dummyid" onLoad={async () => {}} />);
});
const responseModalElement = screen.getByRole('presentation');

Expand All @@ -58,7 +58,7 @@ describe('Token response modal', () => {
it('becomes invisible when the "Close" button is clicked', async () => {
// Given...
await act(async () => {
return render(<TokenResponseModal refreshToken="dummytoken" clientId="dummyid" clientSecret="dummysecret" onLoad={async () => {}} />);
return render(<TokenResponseModal refreshToken="dummytoken" clientId="dummyid" onLoad={async () => {}} />);
});
const modalCloseButtonElement = screen.getByLabelText(/close/i);
const responseModalElement = screen.getByRole('presentation');
Expand Down
3 changes: 1 addition & 2 deletions galasa-ui/src/tests/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe('Middleware', () => {
postAuthenticateSpy.mockReset();
});

it('should set a refresh token cookie during a callback request with client ID and secret cookies', async () => {
it('should set a refresh token cookie during a callback request with client ID cookie', async () => {
// Given...
redirectSpy.mockRestore();

Expand All @@ -156,7 +156,6 @@ describe('Middleware', () => {
const redirectUrl = 'http://my-connector/auth';

req.cookies.set('client_id', 'my-client-id');
req.cookies.set('client_secret', 'shhh');

global.fetch = jest.fn(() =>
Promise.resolve({
Expand Down
3 changes: 1 addition & 2 deletions galasa-ui/src/tests/routes/authTokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jest.mock('@/generated/galasaapi', () => ({
postClients: jest.fn().mockReturnValue(
Promise.resolve({
clientId: 'dummy-id',
clientSecret: 'shhh',
})
),
})),
Expand Down Expand Up @@ -73,7 +72,7 @@ describe('POST /auth/tokens', () => {
mockAuthenticationApi.mockReset();
});

it('throws an error if the newly created Dex client does not contain a client ID and secret', async () => {
it('throws an error if the newly created Dex client does not contain a client ID', async () => {
// Given...
const redirectUrl = 'http://my-connector/auth';

Expand Down
8 changes: 3 additions & 5 deletions galasa-ui/src/utils/authCookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@
*/

const AuthCookies = {
CLIENT_ID: "client_id",
CLIENT_SECRET: "client_secret",
REFRESH_TOKEN: "refresh_token",
ID_TOKEN: "id_token",
STATE: "state",
CLIENT_ID: 'client_id',
REFRESH_TOKEN: 'refresh_token',
ID_TOKEN: 'id_token',
};

export default AuthCookies;

0 comments on commit 7a7515a

Please sign in to comment.