diff --git a/aws/cognito/clients.tf b/aws/cognito/clients.tf index ff710d133..2a5324433 100644 --- a/aws/cognito/clients.tf +++ b/aws/cognito/clients.tf @@ -80,7 +80,15 @@ resource "aws_cognito_user_pool_client" "nextstrain-cli" { name = "nextstrain-cli" - # Allow Secure Remote Password (SRP) auth, plus refresh token auth (required). + # Allow client to use OAuth (with the authorization code grant type only) + # against the user pool, plus Secure Remote Password (SRP) auth and refresh + # token auth (required). + allowed_oauth_flows_user_pool_client = true + allowed_oauth_flows = ["code"] + allowed_oauth_scopes = ["email", "openid", "phone", "profile"] + + supported_identity_providers = ["COGNITO"] + explicit_auth_flows = [ "ALLOW_USER_SRP_AUTH", "ALLOW_REFRESH_TOKEN_AUTH", @@ -96,6 +104,39 @@ resource "aws_cognito_user_pool_client" "nextstrain-cli" { refresh_token = "days" } + # Allowed redirection destinations to complete authentication. + # + # We'd prefer to use 127.0.0.1 instead of localhost to avoid name resolution + # issues on end user systems, issues which are known to occur. The "OAuth + # 2.0 for native apps" best current practice (RFC 8252) suggests as much¹, but + # alas, Cognito's https-requirement exception for localhost is not applied to + # 127.0.0.1. + # + # Similarly, we'd prefer to register without an explicit port and rely on the + # same RFC's stipulation of relaxed port matching for localhost², but alack, + # Cognito doesn't follow that either and requires strict port matching. + # + # Since the CLI may not always be able to listen on a specific port given + # other services that might be running, and there's also value in random + # choice making interception harder, register a slew of ports for use and let + # Nextstrain CLI draw from the list. + # -trs, 19 Nov 2023 + # + # ¹ + # ² + callback_urls = formatlist("http://localhost:%d/", random_integer.nextstrain_cli_callback_port[*].result) + read_attributes = local.user_attributes write_attributes = setsubtract(local.user_attributes, ["email_verified", "phone_number_verified"]) } + +resource "random_integer" "nextstrain_cli_callback_port" { + # AWS Cognito supports 100 callback URLs per client + # + count = 99 + + # IANA-defined port range for dynamic use. + # + min = 49152 + max = 65535 +} diff --git a/aws/cognito/outputs.tf b/aws/cognito/outputs.tf index e8cdd8cfe..edc3878f0 100644 --- a/aws/cognito/outputs.tf +++ b/aws/cognito/outputs.tf @@ -14,6 +14,10 @@ output "OAUTH2_CLI_CLIENT_ID" { value = aws_cognito_user_pool_client.nextstrain-cli.id } +output "OAUTH2_CLI_CLIENT_REDIRECT_URIS" { + value = aws_cognito_user_pool_client.nextstrain-cli.callback_urls +} + output "OAUTH2_LOGOUT_URL" { value = format("https://%s/logout", coalesce( one(aws_cognito_user_pool_domain.custom[*].domain), diff --git a/aws/iam/policy/NextstrainDotOrgServerInstance-testing.tftpl.json b/aws/iam/policy/NextstrainDotOrgServerInstance-testing.tftpl.json index b6f210543..c7efe204e 100644 --- a/aws/iam/policy/NextstrainDotOrgServerInstance-testing.tftpl.json +++ b/aws/iam/policy/NextstrainDotOrgServerInstance-testing.tftpl.json @@ -55,6 +55,16 @@ "Resource": [ "arn:aws:cognito-idp:us-east-1:827581582529:userpool/${COGNITO_USER_POOL_ID}" ] + }, + { + "Sid": "GetResourcesIndex", + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::nextstrain-inventories/resources.json.gz" + ] } ] } diff --git a/aws/iam/policy/NextstrainDotOrgServerInstance.tftpl.json b/aws/iam/policy/NextstrainDotOrgServerInstance.tftpl.json index 3d423d6b5..a3594d510 100644 --- a/aws/iam/policy/NextstrainDotOrgServerInstance.tftpl.json +++ b/aws/iam/policy/NextstrainDotOrgServerInstance.tftpl.json @@ -44,6 +44,16 @@ "Resource": [ "arn:aws:cognito-idp:us-east-1:827581582529:userpool/${COGNITO_USER_POOL_ID}" ] + }, + { + "Sid": "GetResourcesIndex", + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::nextstrain-inventories/resources.json.gz" + ] } ] } diff --git a/docs/production.rst b/docs/production.rst index b8f6e8452..c26e6fd35 100644 --- a/docs/production.rst +++ b/docs/production.rst @@ -239,6 +239,7 @@ file are:: OAUTH2_CLIENT_ID OAUTH2_CLIENT_SECRET OAUTH2_CLI_CLIENT_ID + OAUTH2_CLI_CLIENT_REDIRECT_URIS OIDC_USERNAME_CLAIM OIDC_GROUPS_CLAIM @@ -255,6 +256,9 @@ Clients Two OAuth 2.0 clients (sometimes called "applications") must be registered with the IdP. +App server client +~~~~~~~~~~~~~~~~~ + A `confidential, web application client `__ is required for use by the app server to implement browser-based sessions. Its id and secret are configured by `OAUTH2_CLIENT_ID` and `OAUTH2_CLIENT_SECRET`. The app @@ -270,24 +274,52 @@ server does not strictly require a secret. The client registration must allow: - a logout redirection URL of `https://` -Token lifetimes for this client should be configured with consideration that -the id token lifetime affects how often background renewal requests are -necessary and the refresh token lifetime limits the maximum duration of web -sessions. +.. _oauth2-clients: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1 +.. _PKCE: https://datatracker.ietf.org/doc/html/rfc7636 + +CLI client +~~~~~~~~~~ A `public, native application client `__ is required for use by the :doc:`Nextstrain CLI ` and is permitted by the app server to make `Bearer`-authenticated requests. Its id is configured by -`OAUTH2_CLI_CLIENT_ID`. +`OAUTH2_CLI_CLIENT_ID`. The client registration must allow: -.. note:: - Currently Nextstrain CLI is tightly bound to AWS Cognito and requires - its Secure Remote Password authentication flow implemented outside of - the standard OAuth 2.0 flows. We anticipate changing this in the - future. + - the authorization code flow, ideally with PKCE_ support -.. _oauth2-clients: https://datatracker.ietf.org/doc/html/rfc6749#section-2.1 -.. _PKCE: https://datatracker.ietf.org/doc/html/rfc7636 + - issuance of refresh tokens, either by default or by requesting the + `offline_access` scope + + - at least one authentication redirection (sometimes "callback") URL of + `http://127.0.0.1:/` or `http://localhost:/` + +The CLI auto-discovers its OpenID client configuration (and the IdP +configuration) from the app server. The app server must be configured to know +the CLI client's redirect URIs with `OAUTH2_CLI_CLIENT_REDIRECT_URIS` so the +URLs can be included in the discovery response. + +If the IdP allows for `http://` redirect URIs for loopback IPs (e.g. +`127.0.0.1`), then the loopback IP should be preferred over using `localhost`, +as per best current practice described in `RFC 8252 § 8.3`_. + +If the IdP allows relaxed port matching for loopback IP/localhost redirect +URIs, as per best current practice described in `RFC 8252 § 7.3`_, then only a +single redirect URI needs to be registered with the IdP. Otherwise, multiple +redirect URIs with varying ports should be registered to allow the CLI +alternatives to choose from in case it can't bind a given port on a user's +computer. + +.. _RFC 8252 § 7.3: https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 +.. _RFC 8252 § 8.3: https://datatracker.ietf.org/doc/html/rfc8252#section-8.3 + + +Token lifetimes +~~~~~~~~~~~~~~~ + +Token lifetimes for the clients should be configured with consideration that +the id token lifetime affects how often background renewal requests are +necessary and the refresh token lifetime limits the maximum duration of web or +CLI sessions. Authorization role groups diff --git a/env/outputs.tf b/env/outputs.tf index f52d611d5..595107bf6 100644 --- a/env/outputs.tf +++ b/env/outputs.tf @@ -23,6 +23,10 @@ output "OAUTH2_CLI_CLIENT_ID" { value = module.cognito.OAUTH2_CLI_CLIENT_ID } +output "OAUTH2_CLI_CLIENT_REDIRECT_URIS" { + value = module.cognito.OAUTH2_CLI_CLIENT_REDIRECT_URIS +} + output "OAUTH2_LOGOUT_URL" { value = module.cognito.OAUTH2_LOGOUT_URL } diff --git a/env/production/.terraform.lock.hcl b/env/production/.terraform.lock.hcl index 4681d02a9..c114af0a0 100644 --- a/env/production/.terraform.lock.hcl +++ b/env/production/.terraform.lock.hcl @@ -20,3 +20,22 @@ provider "registry.terraform.io/hashicorp/aws" { "zh:f4b86e7df4e846a38774e8e648b41c5ebaddcefa913cfa1864568086b7735575", ] } + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + hashes = [ + "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} diff --git a/env/production/config.json b/env/production/config.json index d27393b06..c5e6a168d 100644 --- a/env/production/config.json +++ b/env/production/config.json @@ -3,6 +3,107 @@ "OIDC_IDP_URL": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_Cg5rcTged", "OAUTH2_CLIENT_ID": "rki99ml8g2jb9sm1qcq9oi5n", "OAUTH2_CLI_CLIENT_ID": "2vmc93kj4fiul8uv40uqge93m5", + "OAUTH2_CLI_CLIENT_REDIRECT_URIS": [ + "http://localhost:49154/", + "http://localhost:49208/", + "http://localhost:49233/", + "http://localhost:49278/", + "http://localhost:49852/", + "http://localhost:50049/", + "http://localhost:50146/", + "http://localhost:50208/", + "http://localhost:50290/", + "http://localhost:50552/", + "http://localhost:50555/", + "http://localhost:50560/", + "http://localhost:50978/", + "http://localhost:51122/", + "http://localhost:51182/", + "http://localhost:51357/", + "http://localhost:51494/", + "http://localhost:51716/", + "http://localhost:51838/", + "http://localhost:51841/", + "http://localhost:51861/", + "http://localhost:51924/", + "http://localhost:52109/", + "http://localhost:52176/", + "http://localhost:52191/", + "http://localhost:52258/", + "http://localhost:52560/", + "http://localhost:52629/", + "http://localhost:53113/", + "http://localhost:53369/", + "http://localhost:53995/", + "http://localhost:54137/", + "http://localhost:54211/", + "http://localhost:54378/", + "http://localhost:54568/", + "http://localhost:54971/", + "http://localhost:55027/", + "http://localhost:55341/", + "http://localhost:55396/", + "http://localhost:55535/", + "http://localhost:55536/", + "http://localhost:55555/", + "http://localhost:55610/", + "http://localhost:55825/", + "http://localhost:56014/", + "http://localhost:56361/", + "http://localhost:56691/", + "http://localhost:56846/", + "http://localhost:56978/", + "http://localhost:57264/", + "http://localhost:57282/", + "http://localhost:57578/", + "http://localhost:57856/", + "http://localhost:57875/", + "http://localhost:58039/", + "http://localhost:58199/", + "http://localhost:58638/", + "http://localhost:59095/", + "http://localhost:59462/", + "http://localhost:59507/", + "http://localhost:59628/", + "http://localhost:59804/", + "http://localhost:59906/", + "http://localhost:59942/", + "http://localhost:60139/", + "http://localhost:60257/", + "http://localhost:60377/", + "http://localhost:60564/", + "http://localhost:60579/", + "http://localhost:60705/", + "http://localhost:60775/", + "http://localhost:61015/", + "http://localhost:61309/", + "http://localhost:61376/", + "http://localhost:61384/", + "http://localhost:61399/", + "http://localhost:61588/", + "http://localhost:61915/", + "http://localhost:62350/", + "http://localhost:62478/", + "http://localhost:62752/", + "http://localhost:62947/", + "http://localhost:63087/", + "http://localhost:63124/", + "http://localhost:63230/", + "http://localhost:63257/", + "http://localhost:63514/", + "http://localhost:63519/", + "http://localhost:63638/", + "http://localhost:63692/", + "http://localhost:63838/", + "http://localhost:64029/", + "http://localhost:64098/", + "http://localhost:64294/", + "http://localhost:64873/", + "http://localhost:65081/", + "http://localhost:65266/", + "http://localhost:65271/", + "http://localhost:65311/" + ], "OAUTH2_LOGOUT_URL": "https://login.nextstrain.org/logout", "COGNITO_USER_POOL_ID": "us-east-1_Cg5rcTged", "OIDC_USERNAME_CLAIM": "cognito:username", diff --git a/env/testing/.terraform.lock.hcl b/env/testing/.terraform.lock.hcl index 4681d02a9..c114af0a0 100644 --- a/env/testing/.terraform.lock.hcl +++ b/env/testing/.terraform.lock.hcl @@ -20,3 +20,22 @@ provider "registry.terraform.io/hashicorp/aws" { "zh:f4b86e7df4e846a38774e8e648b41c5ebaddcefa913cfa1864568086b7735575", ] } + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + hashes = [ + "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} diff --git a/env/testing/config.json b/env/testing/config.json index 29e05b6f6..68af5aafe 100644 --- a/env/testing/config.json +++ b/env/testing/config.json @@ -3,6 +3,106 @@ "OIDC_IDP_URL": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_zqpCrjM7I", "OAUTH2_CLIENT_ID": "6qiojrhr8tibt0f6hphnm1osp1", "OAUTH2_CLI_CLIENT_ID": "9opa27o74f4jsq8g4a34e1mqr", + "OAUTH2_CLI_CLIENT_REDIRECT_URIS": [ + "http://localhost:49161/", + "http://localhost:49334/", + "http://localhost:49359/", + "http://localhost:49398/", + "http://localhost:49603/", + "http://localhost:50044/", + "http://localhost:50110/", + "http://localhost:50132/", + "http://localhost:50467/", + "http://localhost:50667/", + "http://localhost:50712/", + "http://localhost:51264/", + "http://localhost:51333/", + "http://localhost:51413/", + "http://localhost:51467/", + "http://localhost:51596/", + "http://localhost:51664/", + "http://localhost:51953/", + "http://localhost:51974/", + "http://localhost:51977/", + "http://localhost:52272/", + "http://localhost:52342/", + "http://localhost:52361/", + "http://localhost:52564/", + "http://localhost:52621/", + "http://localhost:52673/", + "http://localhost:53216/", + "http://localhost:53267/", + "http://localhost:53375/", + "http://localhost:53624/", + "http://localhost:53644/", + "http://localhost:54071/", + "http://localhost:55078/", + "http://localhost:55286/", + "http://localhost:55296/", + "http://localhost:55357/", + "http://localhost:55419/", + "http://localhost:55462/", + "http://localhost:55724/", + "http://localhost:56972/", + "http://localhost:57135/", + "http://localhost:57194/", + "http://localhost:57255/", + "http://localhost:57321/", + "http://localhost:57411/", + "http://localhost:57564/", + "http://localhost:57591/", + "http://localhost:57649/", + "http://localhost:57654/", + "http://localhost:57883/", + "http://localhost:58259/", + "http://localhost:58549/", + "http://localhost:58826/", + "http://localhost:59071/", + "http://localhost:59359/", + "http://localhost:59688/", + "http://localhost:60055/", + "http://localhost:60107/", + "http://localhost:60205/", + "http://localhost:60590/", + "http://localhost:60790/", + "http://localhost:60883/", + "http://localhost:60897/", + "http://localhost:60911/", + "http://localhost:61155/", + "http://localhost:61325/", + "http://localhost:61369/", + "http://localhost:61400/", + "http://localhost:61406/", + "http://localhost:61553/", + "http://localhost:62190/", + "http://localhost:62405/", + "http://localhost:62439/", + "http://localhost:62467/", + "http://localhost:62638/", + "http://localhost:62726/", + "http://localhost:63016/", + "http://localhost:63103/", + "http://localhost:63309/", + "http://localhost:63318/", + "http://localhost:63387/", + "http://localhost:63480/", + "http://localhost:63526/", + "http://localhost:63704/", + "http://localhost:63743/", + "http://localhost:63775/", + "http://localhost:64008/", + "http://localhost:64373/", + "http://localhost:64410/", + "http://localhost:64446/", + "http://localhost:64520/", + "http://localhost:64694/", + "http://localhost:64960/", + "http://localhost:64972/", + "http://localhost:65032/", + "http://localhost:65086/", + "http://localhost:65113/", + "http://localhost:65121/" + ], "OAUTH2_LOGOUT_URL": "https://nextstrain-testing.auth.us-east-1.amazoncognito.com/logout", "COGNITO_USER_POOL_ID": "us-east-1_zqpCrjM7I", "OIDC_USERNAME_CLAIM": "cognito:username", diff --git a/src/app.js b/src/app.js index d1d010950..a37b01d19 100644 --- a/src/app.js +++ b/src/app.js @@ -454,6 +454,15 @@ app.route("/schemas/*") .all((req, res, next) => next(new NotFound())); +/* OpenID Connect 1.0 configuration. Retrieved by Nextstrain CLI to + * discovery necessary authentication details. + * + * + */ +app.routeAsync("/.well-known/openid-configuration") + .getAsync(endpoints.openid.providerConfiguration); + + /* Auspice HTML pages and assets. * * Auspice hardcodes URL paths that start with /dist/… in its Webpack config, diff --git a/src/config.js b/src/config.js index f64ce8bcf..7fe400583 100644 --- a/src/config.js +++ b/src/config.js @@ -246,6 +246,24 @@ export const OAUTH2_LOGOUT_URL = fromEnvOrConfig("OAUTH2_LOGOUT_URL", OIDC_CONFI export const OAUTH2_SCOPES_SUPPORTED = new Set(fromEnvOrConfig("OAUTH2_SCOPES_SUPPORTED", OIDC_CONFIGURATION.scopes_supported)); +/** + * Effective OpenID Connect (OIDC) identity provider configuration document + * after potential local overrides. + * + * Defined here to keep the overridden fields close to their declarations + * above. + */ +export const EFFECTIVE_OIDC_CONFIGURATION = { + ...OIDC_CONFIGURATION, + issuer: OIDC_ISSUER_URL, + jwks_uri: OIDC_JWKS_URL, + authorization_endpoint: OAUTH2_AUTHORIZATION_URL, + token_endpoint: OAUTH2_TOKEN_URL, + end_session_endpoint: OAUTH2_LOGOUT_URL, + scopes_supported: OAUTH2_SCOPES_SUPPORTED, +}; + + /** * OAuth2 client id of nextstrain.org server as registered with our IdP (e.g. * our Cognito user pool). @@ -279,6 +297,23 @@ export const OAUTH2_CLIENT_SECRET = fromEnvOrConfig("OAUTH2_CLIENT_SECRET", null export const OAUTH2_CLI_CLIENT_ID = fromEnvOrConfig("OAUTH2_CLI_CLIENT_ID"); +/** + * OAuth2 client redirect URIs (e.g. callback URLs) for Nextstrain CLI as + * registered with the IdP. + * + * These URLs are not themselves used by the server but are provided to + * (discovered by) Nextstrain CLI in a client configuration section of the + * OpenID configuration document served at /.well-known/openid-configuration. + * + * The name of this config var uses "redirect_uri" as the term since that's the + * literal field name used by the OIDC/OAuth2 specs in several places (initial + * auth requests, client metadata registration/querying, etc.). + * + * @type {string[]} + */ +export const OAUTH2_CLI_CLIENT_REDIRECT_URIS = fromEnvOrConfig("OAUTH2_CLI_CLIENT_REDIRECT_URIS"); + + /** * ID token claim field containing the username for a user. * diff --git a/src/endpoints/index.js b/src/endpoints/index.js index 6a9a57b4e..31d1870a7 100644 --- a/src/endpoints/index.js +++ b/src/endpoints/index.js @@ -1,6 +1,7 @@ import * as charon from './charon/index.js'; import * as cli from './cli.js'; import * as groups from "./groups.js"; +import * as openid from './openid.js'; import * as options from './options.js'; import * as sources from './sources.js'; import * as static_ from './static.js'; @@ -10,6 +11,7 @@ export { charon, cli, groups, + openid, options, sources, static_ as static, diff --git a/src/endpoints/openid.js b/src/endpoints/openid.js new file mode 100644 index 000000000..e1e778205 --- /dev/null +++ b/src/endpoints/openid.js @@ -0,0 +1,68 @@ +/** + * OpenID Connect 1.0 endpoints. + * + * @module endpoints.openid + */ + +import { + EFFECTIVE_OIDC_CONFIGURATION, + OAUTH2_CLI_CLIENT_ID, + OAUTH2_CLI_CLIENT_REDIRECT_URIS, + OIDC_USERNAME_CLAIM, + OIDC_GROUPS_CLAIM, + COGNITO_USER_POOL_ID, +} from "../config.js"; + + +/** + * A client configuration document for Nextstrain CLI to automatically discover + * the client metadata it should use for itself. + * + * Based on the OIDC dynamic client registration spec, see + * {@link https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata} and + * {@link https://openid.net/specs/openid-connect-registration-1_0.html#ReadResponse}. + */ +const cliClientConfiguration = { + client_id: OAUTH2_CLI_CLIENT_ID, + + // Static/assumed values asserted by the CLI, but informative to include. + application_type: "native", + response_types: ["code"], + grant_types: ["authorization_code"], + + /* Used to know the list of ports that can be listened to on localhost, as + * not all IdPs follow RFC 8252 § 7.3.¹ + * + * ¹ + */ + redirect_uris: OAUTH2_CLI_CLIENT_REDIRECT_URIS, + + /******************************************** + * Custom metadata fields below this point. * + ********************************************/ + + /* Used by the CLI for display purposes only, but nice to get the display + * right. + */ + id_token_username_claim: OIDC_USERNAME_CLAIM, + id_token_groups_claim: OIDC_GROUPS_CLAIM, + + /* Used for Secure Remote Password auth flow with Cognito outside the + * OIDC/OAuth2 protocols. + */ + aws_cognito_user_pool_id: COGNITO_USER_POOL_ID, +}; + + +/** + * IdP metadata for the /.well-known/openid-configuration endpoint. + * + * As the spec allows, we extend the metadata with a client configuration + * section for Nextstrain CLI to allow it to perform automatic discovery. + * + * Refer to {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata}. + */ +export const providerConfiguration = (req, res) => res.json({ + ...EFFECTIVE_OIDC_CONFIGURATION, + nextstrain_cli_client_configuration: cliClientConfiguration, +});