diff --git a/src/core/server/app/middleware/passport/strategies/oidc/discover.ts b/src/core/server/app/middleware/passport/strategies/oidc/discover.ts new file mode 100644 index 000000000..afc14afc6 --- /dev/null +++ b/src/core/server/app/middleware/passport/strategies/oidc/discover.ts @@ -0,0 +1,47 @@ +import fetch from "node-fetch"; +import { URL } from "url"; + +/** + * Configuration that Talk is expecting. + */ +export interface DiscoveryConfiguration { + authorizationURL?: string; + tokenURL?: string; + jwksURI?: string; +} + +/** + * Subset of configuration as defined in the set of Provider Metadata: + * + * https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata + */ +interface DiscoveryRawConfiguration { + authorization_endpoint?: string; + token_endpoint?: string; + jwks_uri?: string; +} + +/** + * discover will discover the configuration for the issuer. + * + * @param issuer the Issuer URL that should be used to determine the + * configuration + */ +export async function discover(issuer: URL): Promise { + // Any provider MUST provide a .well-known url that is JSON parsable based + // on the issuer: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + const configurationURL = + issuer.origin + + issuer.pathname.replace(/\/$/, "") + + "/.well-known/openid-configuration"; + const res = await fetch(configurationURL); + + // Parse the configuration + const meta: DiscoveryRawConfiguration = await res.json(); + + return { + authorizationURL: meta.authorization_endpoint, + tokenURL: meta.token_endpoint, + jwksURI: meta.jwks_uri, + }; +} diff --git a/src/core/server/app/middleware/passport/strategies/oidc.ts b/src/core/server/app/middleware/passport/strategies/oidc/index.ts similarity index 100% rename from src/core/server/app/middleware/passport/strategies/oidc.ts rename to src/core/server/app/middleware/passport/strategies/oidc/index.ts diff --git a/src/core/server/app/middleware/passport/strategies/oidc.spec.ts b/src/core/server/app/middleware/passport/strategies/oidc/oidc.spec.ts similarity index 100% rename from src/core/server/app/middleware/passport/strategies/oidc.spec.ts rename to src/core/server/app/middleware/passport/strategies/oidc/oidc.spec.ts diff --git a/src/core/server/graph/tenant/mutators/settings.ts b/src/core/server/graph/tenant/mutators/settings.ts index d678f5c61..079a9389e 100644 --- a/src/core/server/graph/tenant/mutators/settings.ts +++ b/src/core/server/graph/tenant/mutators/settings.ts @@ -1,13 +1,24 @@ import { isNull, omitBy } from "lodash"; import TenantContext from "talk-server/graph/tenant/context"; -import { GQLSettingsInput } from "talk-server/graph/tenant/schema/__generated__/types"; +import { + GQLDiscoverOIDCConfigurationInput, + GQLOIDCConfiguration, + GQLSettingsInput, +} from "talk-server/graph/tenant/schema/__generated__/types"; import { Tenant } from "talk-server/models/tenant"; -import { regenerateSSOKey, update } from "talk-server/services/tenant"; +import { + discoverOIDCConfiguration, + regenerateSSOKey, + update, +} from "talk-server/services/tenant"; export default ({ mongo, redis, tenantCache, tenant }: TenantContext) => ({ update: (input: GQLSettingsInput): Promise => update(mongo, redis, tenantCache, tenant, omitBy(input, isNull)), regenerateSSOKey: (): Promise => regenerateSSOKey(mongo, redis, tenantCache, tenant), + discoverOIDCConfiguration: ( + input: GQLDiscoverOIDCConfigurationInput + ): Promise => discoverOIDCConfiguration(input.issuer), }); diff --git a/src/core/server/graph/tenant/resolvers/mutation.ts b/src/core/server/graph/tenant/resolvers/mutation.ts index 1b308ff4f..7255071d7 100644 --- a/src/core/server/graph/tenant/resolvers/mutation.ts +++ b/src/core/server/graph/tenant/resolvers/mutation.ts @@ -51,6 +51,10 @@ const Mutation: GQLMutationTypeResolver = { settings: await ctx.mutators.Settings.regenerateSSOKey(), clientMutationId: input.clientMutationId, }), + discoverOIDCConfiguration: async (source, { input }, ctx) => ({ + configuration: await ctx.mutators.Settings.discoverOIDCConfiguration(input), + clientMutationId: input.clientMutationId, + }), }; export default Mutation; diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index 268e303c4..65cde5509 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -1900,6 +1900,41 @@ type RegenerateSSOKeyPayload { clientMutationId: String! } +################## +## discoverOIDCConfiguration +################## + +input DiscoverOIDCConfigurationInput { + """ + + """ + issuer: String! + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + +type OIDCConfiguration { + authorizationURL: String + tokenURL: String + jwksURI: String +} + +type DiscoverOIDCConfigurationPayload { + """ + configuration was the discovered configuration for the OpenID Connect server + given the provided issuer. + """ + configuration: OIDCConfiguration + + """ + clientMutationId is required for Relay support. + """ + clientMutationId: String! +} + ################## ## Mutation ################## @@ -1929,6 +1964,10 @@ type Mutation { regenerateSSOKey(input: RegenerateSSOKeyInput!): RegenerateSSOKeyPayload @auth(roles: [ADMIN]) + discoverOIDCConfiguration( + input: DiscoverOIDCConfigurationInput! + ): DiscoverOIDCConfigurationPayload @auth(roles: [ADMIN]) + """ createCommentReaction will create a Reaction authored by the current logged in User on a Comment. diff --git a/src/core/server/services/tenant/index.ts b/src/core/server/services/tenant/index.ts index 8bcfca5eb..2d0f634a9 100644 --- a/src/core/server/services/tenant/index.ts +++ b/src/core/server/services/tenant/index.ts @@ -1,5 +1,6 @@ import { Redis } from "ioredis"; import { Db } from "mongodb"; +import { URL } from "url"; import { GQLSettingsInput } from "talk-server/graph/tenant/schema/__generated__/types"; import { @@ -10,6 +11,7 @@ import { updateTenant, } from "talk-server/models/tenant"; +import { discover } from "talk-server/app/middleware/passport/strategies/oidc/discover"; import logger from "talk-server/logger"; import TenantCache from "./cache"; @@ -98,3 +100,19 @@ export async function regenerateSSOKey( return updatedTenant; } + +/** + * discoverOIDCConfiguration will discover the OpenID Connect configuration as + * is required by any OpenID Connect compatible service: + * + * https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig + * + * @param issuerString the issuer that should be used as the discovery root. + */ +export async function discoverOIDCConfiguration(issuerString: string) { + // Parse the issuer. + const issuer = new URL(issuerString); + + // Discover the configuration. + return discover(issuer); +} diff --git a/src/types/webfinger.d.ts b/src/types/webfinger.d.ts deleted file mode 100644 index a13bfb998..000000000 --- a/src/types/webfinger.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -declare module "webfinger" { - export interface WebfingerOptions { - webfingerOnly?: boolean; - } - - export interface WebfingerCallback { - (err: Error, jrd: { [key: string]: any }): void; - } - - export function webfinger( - resource: string, - res: string, - options: WebfingerOptions, - callback: WebfingerCallback - ): void; -}