From 36225e443b8719f1ad62ada13b0c6e0fe8654d4e Mon Sep 17 00:00:00 2001 From: Tessa Thornton Date: Tue, 10 Sep 2019 04:06:07 -0400 Subject: [PATCH] [CORL-556] Allow session length configuration (#2530) * make session duration configurable by tenant * fix expiration date calculation * add form field for session length * use constant for default session * update spec * update fixtures * add missing translation * update snap * fix: use shared variable instead of hardcoded value for tests * fix: adjusted missing expires in parameter --- .../Configure/sections/Auth/AuthConfig.tsx | 9 +- .../sections/Auth/AuthConfigContainer.tsx | 1 + .../sections/Auth/AuthConfigRoute.tsx | 1 + .../Configure/sections/Auth/SessionConfig.tsx | 54 ++++++++++++ .../sections/Auth/SessionConfigContainer.tsx | 34 +++++++ .../__snapshots__/auth.spec.tsx.snap | 88 ++++++++++++++++++- src/core/client/admin/test/fixtures.ts | 7 +- src/core/common/constants.ts | 5 ++ .../server/app/middleware/passport/index.ts | 14 ++- .../server/graph/tenant/schema/schema.graphql | 9 ++ src/core/server/models/tenant/tenant.ts | 2 + src/core/server/services/jwt/index.ts | 3 +- src/locales/en-US/admin.ftl | 7 ++ 13 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 src/core/client/admin/routes/Configure/sections/Auth/SessionConfig.tsx create mode 100644 src/core/client/admin/routes/Configure/sections/Auth/SessionConfigContainer.tsx diff --git a/src/core/client/admin/routes/Configure/sections/Auth/AuthConfig.tsx b/src/core/client/admin/routes/Configure/sections/Auth/AuthConfig.tsx index cd5fbc3d4..a640240ec 100644 --- a/src/core/client/admin/routes/Configure/sections/Auth/AuthConfig.tsx +++ b/src/core/client/admin/routes/Configure/sections/Auth/AuthConfig.tsx @@ -5,10 +5,12 @@ import { HorizontalGutter } from "coral-ui/components"; import { OnInitValuesFct } from "./AuthConfigContainer"; import AuthIntegrationsConfig from "./AuthIntegrationsConfig"; +import SessionConfigContainer from "./SessionConfigContainer"; interface Props { disabled?: boolean; - auth: PropTypesOf["auth"]; + auth: PropTypesOf["auth"] & + PropTypesOf["auth"]; onInitValues: OnInitValuesFct; } @@ -18,6 +20,11 @@ const AuthConfig: FunctionComponent = ({ onInitValues, }) => ( + ({ ...LocalAuthConfigContainer_auth ...OIDCConfigContainer_auth ...OIDCConfigContainer_authReadOnly + ...SessionConfigContainer_auth } `, })( diff --git a/src/core/client/admin/routes/Configure/sections/Auth/AuthConfigRoute.tsx b/src/core/client/admin/routes/Configure/sections/Auth/AuthConfigRoute.tsx index abdd0eba8..c56de4370 100644 --- a/src/core/client/admin/routes/Configure/sections/Auth/AuthConfigRoute.tsx +++ b/src/core/client/admin/routes/Configure/sections/Auth/AuthConfigRoute.tsx @@ -41,6 +41,7 @@ const enhanced = withRouteConfig({ ...AuthConfigContainer_settings auth { ...AuthConfigContainer_auth + ...SessionConfigContainer_auth } } } diff --git a/src/core/client/admin/routes/Configure/sections/Auth/SessionConfig.tsx b/src/core/client/admin/routes/Configure/sections/Auth/SessionConfig.tsx new file mode 100644 index 000000000..7a5a558df --- /dev/null +++ b/src/core/client/admin/routes/Configure/sections/Auth/SessionConfig.tsx @@ -0,0 +1,54 @@ +import { Localized } from "fluent-react/compat"; +import React, { FunctionComponent } from "react"; +import { Field } from "react-final-form"; + +import { DurationField } from "coral-framework/components"; +import { ValidationMessage } from "coral-framework/lib/form"; +import { + composeValidators, + required, + validateWholeNumberGreaterThanOrEqual, +} from "coral-framework/lib/validation"; +import { + FieldSet, + FormField, + HorizontalGutter, + InputLabel, +} from "coral-ui/components"; + +import Header from "../../Header"; + +interface Props { + disabled?: boolean; +} + +const SessionConfig: FunctionComponent = ({ disabled }) => ( + + + +
Session settings
+
+
+ }> + + Session Duration + + + {({ input, meta }) => ( + <> + + + + )} + + +
+); + +export default SessionConfig; diff --git a/src/core/client/admin/routes/Configure/sections/Auth/SessionConfigContainer.tsx b/src/core/client/admin/routes/Configure/sections/Auth/SessionConfigContainer.tsx new file mode 100644 index 000000000..a9fac1ff4 --- /dev/null +++ b/src/core/client/admin/routes/Configure/sections/Auth/SessionConfigContainer.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { graphql } from "react-relay"; + +import { SessionConfigContainer_auth as AuthData } from "coral-admin/__generated__/SessionConfigContainer_auth.graphql"; +import { withFragmentContainer } from "coral-framework/lib/relay"; +import { OnInitValuesFct } from "./AuthConfigContainer"; +import SessionConfig from "./SessionConfig"; + +interface Props { + auth: AuthData; + onInitValues: OnInitValuesFct; + disabled?: boolean; +} + +class SessionConfigContainer extends React.Component { + constructor(props: Props) { + super(props); + props.onInitValues({ auth: props.auth }); + } + + public render() { + const { disabled } = this.props; + return ; + } +} + +const enhanced = withFragmentContainer({ + auth: graphql` + fragment SessionConfigContainer_auth on Auth { + sessionDuration + } + `, +})(SessionConfigContainer); +export default enhanced; diff --git a/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap b/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap index 82e37e149..87cd71fbf 100644 --- a/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap +++ b/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap @@ -2261,7 +2261,8 @@ compliance.

- Missing translation "configure-account-features-delete-account-fieldDescriptions" + Removes all of their comment data, username, and email +address from the site and the database.

+
+
+

+ Session settings +

+
+
+ + Session duration + +
+
+ +
+ + + + + + +
+
+
diff --git a/src/core/client/admin/test/fixtures.ts b/src/core/client/admin/test/fixtures.ts index f9199c641..04b8e401b 100644 --- a/src/core/client/admin/test/fixtures.ts +++ b/src/core/client/admin/test/fixtures.ts @@ -1,4 +1,7 @@ -import { TOXICITY_THRESHOLD_DEFAULT } from "coral-common/constants"; +import { + DEFAULT_SESSION_LENGTH, + TOXICITY_THRESHOLD_DEFAULT, +} from "coral-common/constants"; import { pureMerge } from "coral-common/utils"; import { GQLComment, @@ -86,6 +89,7 @@ export const settings = createFixture({ }, }, auth: { + sessionDuration: DEFAULT_SESSION_LENGTH, integrations: { local: { enabled: true, @@ -159,6 +163,7 @@ export const settingsWithEmptyAuth = createFixture( { id: "settings", auth: { + sessionDuration: DEFAULT_SESSION_LENGTH, integrations: { local: { enabled: true, diff --git a/src/core/common/constants.ts b/src/core/common/constants.ts index 12a3bda8b..802ebda24 100644 --- a/src/core/common/constants.ts +++ b/src/core/common/constants.ts @@ -52,3 +52,8 @@ export const ALLOWED_USERNAME_CHANGE_FREQUENCY = 14 * 86400; * deletion. */ export const SCHEDULED_DELETION_TIMESPAN_DAYS = 14; + +/** + * DEFAULT_SESSION_LENTTH is the length of time in seconds a session is valid for unless configured in tenant. + */ +export const DEFAULT_SESSION_LENGTH = 7776000; diff --git a/src/core/server/app/middleware/passport/index.ts b/src/core/server/app/middleware/passport/index.ts index a5b691dd3..9c6011c23 100644 --- a/src/core/server/app/middleware/passport/index.ts +++ b/src/core/server/app/middleware/passport/index.ts @@ -117,14 +117,18 @@ export async function handleSuccessfulLogin( const tenant = coral.tenant!; // Compute the expiry date. - const expiresIn = DateTime.fromJSDate(coral.now).plus({ days: 1 }); + const expiresIn = DateTime.fromJSDate(coral.now).plus({ + seconds: tenant.auth.sessionDuration, + }); // Grab the token. const token = await signTokenString( signingConfig, user, tenant, - { expiresIn: "1d" }, + { + expiresIn: tenant.auth.sessionDuration, + }, coral.now ); @@ -177,14 +181,16 @@ export async function handleOAuth2Callback( const tenant = req.coral!.tenant!; // Compute the expiry date. - const expiresIn = DateTime.fromJSDate(req.coral!.now).plus({ days: 1 }); + const expiresIn = DateTime.fromJSDate(req.coral!.now).plus({ + seconds: tenant.auth.sessionDuration, + }); // Grab the token. const token = await signTokenString( signingConfig, user, tenant, - { expiresIn: "1d" }, + { expiresIn: tenant.auth.sessionDuration }, req.coral!.now ); res.cookie( diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index c33fdb3b8..e7e3e5190 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -422,6 +422,7 @@ type LocalAuthIntegration { integration should be displayed in all targets. """ targetFilter: AuthenticationTargetFilter! + } ########################## @@ -738,6 +739,10 @@ type Auth { authentication solutions. """ integrations: AuthIntegrations! + """ + sessionDuration determines the duration in seconds for which an access token is valid + """ + sessionDuration: Int! } ################################################################################ @@ -3002,6 +3007,10 @@ input SettingsAuthInput { authentication solutions. """ integrations: SettingsAuthIntegrationsInput + """ + sessionDuration determines the duration in seconds for which an access token is valid + """ + sessionDuration: Int! } input SettingsAkismetExternalIntegrationInput { diff --git a/src/core/server/models/tenant/tenant.ts b/src/core/server/models/tenant/tenant.ts index 2feaf920a..5216ca047 100644 --- a/src/core/server/models/tenant/tenant.ts +++ b/src/core/server/models/tenant/tenant.ts @@ -1,6 +1,7 @@ import { Db } from "mongodb"; import uuid from "uuid"; +import { DEFAULT_SESSION_LENGTH } from "coral-common/constants"; import { LanguageCode } from "coral-common/helpers/i18n/locales"; import { DeepPartial, Omit, Sub } from "coral-common/types"; import { dotize } from "coral-common/utils/dotize"; @@ -114,6 +115,7 @@ export async function createTenant( banned: [], }, auth: { + sessionDuration: DEFAULT_SESSION_LENGTH, integrations: { local: { enabled: true, diff --git a/src/core/server/services/jwt/index.ts b/src/core/server/services/jwt/index.ts index 4a273e72f..99df6d93e 100644 --- a/src/core/server/services/jwt/index.ts +++ b/src/core/server/services/jwt/index.ts @@ -1,4 +1,5 @@ import cookie from "cookie"; +import { DEFAULT_SESSION_LENGTH } from "coral-common/constants"; import { IncomingMessage } from "http"; import { Redis } from "ioredis"; import Joi from "joi"; @@ -229,7 +230,7 @@ export const signTokenString = async ( secret, { jwtid: uuid(), - expiresIn: "1 day", + expiresIn: DEFAULT_SESSION_LENGTH, ...options, issuer: tenant.id, subject: user.id, diff --git a/src/locales/en-US/admin.ftl b/src/locales/en-US/admin.ftl index f1d4419c3..40072cb7a 100644 --- a/src/locales/en-US/admin.ftl +++ b/src/locales/en-US/admin.ftl @@ -242,6 +242,9 @@ configure-auth-oidc-tokenURL = Token URL configure-auth-oidc-jwksURI = JWKS URI configure-auth-oidc-useLoginOn = Use OpenID Connect login on +configure-auth-settings = Session settings +configure-auth-settings-session-duration-label = Session duration + ### Moderation ### Recent Comment History @@ -768,6 +771,10 @@ configure-account-features-delete-account = Delete their account configure-account-features-delete-account-details = Removes all of their comment data, username, and email address from the site and the database. +configure-account-features-delete-account-fieldDescriptions = + Removes all of their comment data, username, and email + address from the site and the database. + configure-advanced-stories = Story creation configure-advanced-stories-explanation = Advanced settings for how stories are created within Coral. configure-advanced-stories-lazy = Lazy story creation