[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
This commit is contained in:
Tessa Thornton
2019-09-10 08:06:07 +00:00
committed by Wyatt Johnson
parent ed8be75774
commit 36225e443b
13 changed files with 226 additions and 8 deletions
@@ -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<typeof AuthIntegrationsConfig>["auth"];
auth: PropTypesOf<typeof AuthIntegrationsConfig>["auth"] &
PropTypesOf<typeof SessionConfigContainer>["auth"];
onInitValues: OnInitValuesFct;
}
@@ -18,6 +20,11 @@ const AuthConfig: FunctionComponent<Props> = ({
onInitValues,
}) => (
<HorizontalGutter size="double" data-testid="configure-authContainer">
<SessionConfigContainer
auth={auth}
disabled={disabled}
onInitValues={onInitValues}
/>
<AuthIntegrationsConfig
disabled={disabled}
auth={auth}
@@ -135,6 +135,7 @@ const enhanced = withFragmentContainer<Props>({
...LocalAuthConfigContainer_auth
...OIDCConfigContainer_auth
...OIDCConfigContainer_authReadOnly
...SessionConfigContainer_auth
}
`,
})(
@@ -41,6 +41,7 @@ const enhanced = withRouteConfig<Props>({
...AuthConfigContainer_settings
auth {
...AuthConfigContainer_auth
...SessionConfigContainer_auth
}
}
}
@@ -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<Props> = ({ disabled }) => (
<HorizontalGutter>
<HorizontalGutter size="full">
<Localized id="configure-auth-settings">
<Header>Session settings</Header>
</Localized>
</HorizontalGutter>
<FormField container={<FieldSet />}>
<Localized id="configure-auth-settings-session-duration-label">
<InputLabel container="legend">Session Duration</InputLabel>
</Localized>
<Field
name="auth.sessionDuration"
validate={composeValidators(
required,
validateWholeNumberGreaterThanOrEqual(0)
)}
>
{({ input, meta }) => (
<>
<DurationField disabled={!!disabled} {...input} />
<ValidationMessage meta={meta} />
</>
)}
</Field>
</FormField>
</HorizontalGutter>
);
export default SessionConfig;
@@ -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<Props> {
constructor(props: Props) {
super(props);
props.onInitValues({ auth: props.auth });
}
public render() {
const { disabled } = this.props;
return <SessionConfig disabled={disabled} />;
}
}
const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment SessionConfigContainer_auth on Auth {
sessionDuration
}
`,
})(SessionConfigContainer);
export default enhanced;
@@ -2261,7 +2261,8 @@ compliance.
<p
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Missing translation "configure-account-features-delete-account-fieldDescriptions"
Removes all of their comment data, username, and email
address from the site and the database.
</p>
</div>
<div
@@ -2326,6 +2327,91 @@ compliance.
className="Box-root HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-authContainer"
>
<div
className="Box-root HorizontalGutter-root HorizontalGutter-full"
>
<div
className="Box-root HorizontalGutter-root HorizontalGutter-full"
>
<h1
className="Box-root Typography-root Typography-heading3 Typography-colorTextPrimary Header-root"
>
Session settings
</h1>
</div>
<fieldset
className="FieldSet-root Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<legend
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
>
Session duration
</legend>
<div
className="Box-root Flex-root Flex-flex Flex-itemGutter gutter"
>
<div
className="TextField-root DurationField-value"
>
<input
aria-label="value"
autoCapitalize="off"
autoComplete="off"
autoCorrect="off"
className="TextField-input TextField-colorRegular TextField-textAlignCenter"
disabled={false}
name="auth.sessionDuration-value"
onChange={[Function]}
placeholder=""
spellCheck={false}
type="text"
value="90"
/>
</div>
<span
className="SelectField-root"
>
<select
aria-label="unit"
className="SelectField-select DurationField-select"
disabled={false}
name="auth.sessionDuration-unit"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
value="86400"
>
<option
value="3600"
>
Hours
</option>
<option
value="86400"
>
Days
</option>
<option
value="604800"
>
Weeks
</option>
</select>
<span
aria-hidden={true}
className="SelectField-afterWrapper"
>
<i
aria-hidden="true"
className="Icon-root Icon-sm"
>
expand_more
</i>
</span>
</span>
</div>
</fieldset>
</div>
<div
className="Box-root HorizontalGutter-root HorizontalGutter-double"
>
+6 -1
View File
@@ -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<GQLSettings>({
},
},
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
integrations: {
local: {
enabled: true,
@@ -159,6 +163,7 @@ export const settingsWithEmptyAuth = createFixture<GQLSettings>(
{
id: "settings",
auth: {
sessionDuration: DEFAULT_SESSION_LENGTH,
integrations: {
local: {
enabled: true,
+5
View File
@@ -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;
@@ -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(
@@ -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 {
+2
View File
@@ -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,
+2 -1
View File
@@ -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,
+7
View File
@@ -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