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

SSE-3233 - Frontend for Client authentication #709

Merged
merged 2 commits into from
May 28, 2024
Merged
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
160 changes: 79 additions & 81 deletions express/src/controllers/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const showClient: RequestHandler = async (req, res) => {
const redirectUris = client.redirectUris;
const userSectorIdentifierUri = client.sector_identifier_uri;
const userPublicKey = client.publicKey == defaultPublicKey ? "" : getAuthApiCompliantPublicKey(client.publicKey);
const secretHash = client.client_secret ? client.client_secret : "";
const displayedKey: string = client.token_endpoint_auth_method === "client_secret_post" ? secretHash : userPublicKey;
const contacts = client.contacts;
const claims = client.identity_verification_enabled && client.hasOwnProperty("claims") ? client.claims : [];

Expand All @@ -44,6 +46,7 @@ export const showClient: RequestHandler = async (req, res) => {
postLogoutRedirectUris: client.postLogoutUris,
claims: claims,
idTokenSigningAlgorithm: client.hasOwnProperty("id_token_signing_algorithm") ? client.id_token_signing_algorithm : "",
displayedKey: displayedKey,
contacts: contacts,
identityVerificationEnabled: client.identity_verification_enabled,
authMethod: client.token_endpoint_auth_method,
Expand All @@ -56,9 +59,6 @@ export const showClient: RequestHandler = async (req, res) => {
changeScopes: `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/change-scopes?scopes=${encodeURIComponent(
client.scopes.join(" ")
)}`,
changePublicKey: `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/change-public-key?publicKey=${encodeURIComponent(
userPublicKey
)}`,
changePostLogoutUris:
client.postLogoutUris.length === 0
? `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/add-post-logout-uri`
Expand All @@ -72,6 +72,14 @@ export const showClient: RequestHandler = async (req, res) => {
changeClaims: `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/change-claims?claims=${encodeURIComponent(
claims.join(" ")
)}`,
changeKeyUri:
client.token_endpoint_auth_method === "client_secret_post"
? `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/enter-client-secret-hash?secretHash=${encodeURIComponent(
displayedKey
)}`
: `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/change-public-key?publicKey=${encodeURIComponent(
displayedKey
)}`,
changeContacts: `/services/${serviceId}/clients/${authClientId}/${selfServiceClientId}/enter-contact`
}
});
Expand Down Expand Up @@ -170,19 +178,10 @@ export const processPublicBetaForm: RequestHandler = async (req, res) => {
.catch(reason => {
console.error("updatePublicBetaSpreadsheet: " + reason);
});
const s4: SelfServiceServicesService = req.app.get("backing-service");
const userId = AuthenticationResultParser.getCognitoId(nonNull(req.session.authenticationResult));
s4.sendTxMALog(
"SSE_PUBLIC_BETA_FORM_SUBMITTED",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: serviceId
}
);

sendTxMALog(req, userId, "SSE_PUBLIC_BETA_FORM_SUBMITTED");

res.redirect(`/services/${serviceId}/clients/${clientId}/${selfServiceClientId}/public-beta/submitted`);
};

Expand Down Expand Up @@ -270,29 +269,41 @@ export const processChangePublicKeyForm: RequestHandler = async (req, res) => {
req.session.updatedField = "public key";

if (req.params.selfServiceClientId !== "") {
s4.sendTxMALog(
"SSE_UPDATE_PUBLIC_KEY",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);
sendTxMALog(req, userId, "SSE_UPDATE_PUBLIC_KEY");
} else {
s4.sendTxMALog(
"SSE_PUBLIC_KEY_ADDED",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);
sendTxMALog(req, userId, "SSE_PUBLIC_KEY_ADDED");
}

res.redirect(`/services/${req.context.serviceId}/clients`);
};

export const showEnterClientSecretHashForm: RequestHandler = (req, res) => {
res.render("clients/enter-client-secret-hash.njk", {
serviceId: req.context.serviceId,
selfServiceClientId: req.params.selfServiceClientId,
clientId: req.params.clientId,
secretHash: req.query.secretHash
});
};

export const processEnterClientSecretHashForm: RequestHandler = async (req, res) => {
const s4: SelfServiceServicesService = req.app.get("backing-service");
const userId = AuthenticationResultParser.getCognitoId(nonNull(req.session.authenticationResult));

await s4.updateClient(
nonNull(req.context.serviceId),
req.params.selfServiceClientId,
req.params.clientId,
{client_secret: req.body.secretHash},
nonNull(req.session.authenticationResult?.AccessToken)
);

req.session.updatedField = "secret hash";

if (req.params.selfServiceClientId !== "") {
sendTxMALog(req, userId, "SSE_UPDATE_SECRET_HASH");
} else {
sendTxMALog(req, userId, "SSE_SECRET_HASH_ADDED");
}

res.redirect(`/services/${req.context.serviceId}/clients`);
Expand Down Expand Up @@ -358,17 +369,7 @@ export const processAddRedirectUriForm: RequestHandler = async (req, res) => {
nonNull(req.session.authenticationResult?.AccessToken)
);

s4.sendTxMALog(
"SSE_UPDATE_REDIRECT_URL",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);
sendTxMALog(req, userId, "SSE_UPDATE_REDIRECT_URL");

return res.redirect(
`/services/${serviceId}/clients/${req.params.clientId}/${
Expand Down Expand Up @@ -426,17 +427,9 @@ export const processRemoveRedirectUriFrom: RequestHandler = async (req, res) =>
{redirect_uris: newPostLogoutRedirectUris},
nonNull(req.session.authenticationResult?.AccessToken)
);
s4.sendTxMALog(
"SSE_UPDATE_REDIRECT_URL",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);

sendTxMALog(req, userId, "SSE_UPDATE_REDIRECT_URL");

res.redirect(
`/services/${serviceId}/clients/${req.params.clientId}/${
req.params.selfServiceClientId
Expand Down Expand Up @@ -484,6 +477,10 @@ export const processChangeScopesForm: RequestHandler = async (req: Request, res:
nonNull(req.session.authenticationResult?.AccessToken)
);

const userId = AuthenticationResultParser.getCognitoId(nonNull(req.session.authenticationResult));

sendTxMALog(req, userId, "SSE_CHANGE_SCOPES");

req.session.updatedField = "scopes";
res.redirect(`/services/${req.context.serviceId}/clients`);
};
Expand Down Expand Up @@ -548,17 +545,7 @@ export const processAddPostLogoutUriForm: RequestHandler = async (req, res) => {
nonNull(req.session.authenticationResult?.AccessToken)
);

s4.sendTxMALog(
"SSE_UPDATE_LOGOUT_REDIRECT_URL",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);
sendTxMALog(req, userId, "SSE_UPDATE_LOGOUT_REDIRECT_URL");

return res.redirect(
`/services/${serviceId}/clients/${req.params.clientId}/${
Expand Down Expand Up @@ -607,17 +594,9 @@ export const processRemovePostLogoutUriFrom: RequestHandler = async (req, res) =
{post_logout_redirect_uris: newPostLogoutRedirectUris},
nonNull(req.session.authenticationResult?.AccessToken)
);
s4.sendTxMALog(
"SSE_UPDATE_LOGOUT_REDIRECT_URL",
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);

sendTxMALog(req, userId, "SSE_UPDATE_LOGOUT_REDIRECT_URL");

if (newPostLogoutRedirectUris.length) {
res.redirect(
`/services/${serviceId}/clients/${req.params.clientId}/${
Expand Down Expand Up @@ -863,6 +842,25 @@ export const processChangeClaimsForm: RequestHandler = async (req: Request, res:
nonNull(req.session.authenticationResult?.AccessToken)
);

const userId = AuthenticationResultParser.getCognitoId(nonNull(req.session.authenticationResult));

sendTxMALog(req, userId, "SSE_CHANGE_CLAIMS");

req.session.updatedField = "claims";
res.redirect(`/services/${req.context.serviceId}/clients`);
};

const sendTxMALog: (req: Request, userId: string, eventName: string) => void = (req: Request, userId: string, eventName: string): void => {
const s4: SelfServiceServicesService = req.app.get("backing-service");
s4.sendTxMALog(
eventName,
{
session_id: req.session.id,
ip_address: req.ip,
user_id: userId
},
{
service_id: nonNull(req.context.serviceId)
}
);
};
3 changes: 3 additions & 0 deletions express/src/middleware/convert-public-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import getAuthApiCompliantPublicKey, {isPublicKeyValid} from "../lib/public-key"

export default function convertPublicKeyForAuth(req: Request, res: Response, next: NextFunction) {
console.info("In convertPublicKeyForAuth()");
console.info("req.body: " + JSON.stringify(req.body));
console.info("req.path: " + JSON.stringify(req.path));
console.info("req.params: " + JSON.stringify(req.params));

try {
req.body.authCompliantPublicKey = getAuthApiCompliantPublicKey(isPublicKeyValid(req.body.serviceUserPublicKey));
Expand Down
7 changes: 7 additions & 0 deletions express/src/routes/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
processChangeScopesForm,
processConfirmContactRemovalForm,
processEnterContactEmailForm,
processEnterClientSecretHashForm,
processPublicBetaForm,
showChangeBackChannelLogoutUriForm,
showChangePostLogoutUrisForm,
Expand All @@ -18,6 +19,7 @@ import {
showChangeScopesForm,
showClient,
showConfirmContactRemovalForm,
showEnterClientSecretHashForm,
showEnterContactEmailForm,
showEnterContactForm,
showPublicBetaForm,
Expand Down Expand Up @@ -105,3 +107,8 @@ router
.route("/:clientId/:selfServiceClientId/change-sector-identifier-uri")
.get(showChangeSectorIdentifierUriForm)
.post(validateUri("clients/change-sector-identifier-uri.njk", "sectorIdentifierUri", true), processChangeSectorIdentifierUriForm);

router
.route("/:clientId/:selfServiceClientId/enter-client-secret-hash")
.get(showEnterClientSecretHashForm)
.post(processEnterClientSecretHashForm);
1 change: 1 addition & 0 deletions express/src/services/lambda-facade/StubLambdaFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export default class StubLambdaFacade implements LambdaFacadeInterface {
},
data: {S: "SAM Service as a Service Service"},
redirect_uris: convertToAttr(this.redirectUris),
token_endpoint_auth_method: {S: "private_key_jwt"},
sk: {S: "client#d61db4f3-7403-431d-9ead-14cc96476ce4"},
pk: {S: "service#277619fe-c056-45be-bc2a-43310613913c"},
service_type: {S: "MANDATORY"},
Expand Down
44 changes: 24 additions & 20 deletions express/src/views/clients/change-public-key.njk
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
{% extends "layouts/client-form.njk" %}
{% extends "layouts/form.njk" %}
{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %}
{% from "govuk/components/error-summary/macro.njk" import govukErrorSummary %}
{% from "govuk/components/inset-text/macro.njk" import govukInsetText %}
{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %}

{% set pageTitle = "Change public key" %}

{% set publicKeyBlock %}
<p class="govuk-body govuk-!-static-margin-bottom-0 govuk-!-font-weight-bold">Current public key</p>
<p class="govuk-body govuk-!-static-margin-bottom-0" id="current-public-key">{{ serviceUserPublicKey }}</p>
{% set pageTitle = "Enter public key" %}
{% set backLinkPath %}
/services/{{ serviceId }}/clients/
{% endset %}
{% set formCancelUrl = backLinkPath %}
{% set formButtonText = "Confirm" %}

{% block beforeForm %}
<h1 class="govuk-heading-l govuk-!-margin-top-3">Change your public key</h1>
{% block formInputs %}
<main class="govuk-main-wrapper govuk-!-padding-top-7" id="main-content" role="main">
<div class="govuk-width-container">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-form-group">
<h1 class="govuk-label-wrapper">
<label class="govuk-label govuk-label--l" for="more-detail">
Enter public key
</label>
</h1>
{{ form.textAreaInput("", "serviceUserPublicKey", "") }}
</div>
</div>

<p class="govuk-body">You need to
<a href="https://docs.sign-in.service.gov.uk/before-integrating/generate-a-key/" class="govuk-link" rel="noreferrer noopener" target="_blank">
generate a key pair (opens in new tab)</a> and add the public key. This is so our services can securely send messages to each other.
</p>
{{ govukInsetText({
html: publicKeyBlock
}) if serviceUserPublicKey }}
{% endblock %}
</div></div></main>

{% block formInputs %}
{{ form.textAreaInput("Public key", "serviceUserPublicKey",
"Paste in the entire public key in PEM format, including the headers"
) }}
{% endblock %}
Loading
Loading