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

OpenID Connect support #5757

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ee7abad
Support using displayName for signed in state
netpro2k Nov 13, 2020
7a0c6a6
OIDC login
netpro2k Nov 13, 2020
8925f51
Add admin panel config for OIDC
netpro2k Nov 18, 2020
dab2785
Support for string list properties in admin panel
netpro2k Nov 18, 2020
fff7ca5
Fix OIDC login for homepage sign in link
netpro2k Dec 3, 2020
18afc66
Incomplete merge of stale oidc code
rawnsley Oct 12, 2022
e532bd4
Removed duplicate include
rawnsley Oct 12, 2022
2636f93
Adjusted for SignInPage refactoring
rawnsley Oct 12, 2022
88416c6
Adjusted to VerifyPage refactoring
rawnsley Oct 12, 2022
a600389
Presence list has been refactored into PresenceSidebarContainer
rawnsley Oct 12, 2022
f2ca22c
Fixed deprecated flag logic and removed 'show_oidc_configs' as unnece…
rawnsley Oct 13, 2022
1c3fe67
Added missing include in Header
rawnsley Oct 13, 2022
0611b10
Added missing export directive
rawnsley Oct 13, 2022
d3aa912
Verification is not longer just for emails
rawnsley Oct 17, 2022
d7dca25
OIDC support for modal sign-in dialog (from room context)
rawnsley Oct 17, 2022
129f389
Removed deprecate file
rawnsley Oct 17, 2022
40a52e8
Match OIDC dialog to email dialog
rawnsley Oct 17, 2022
59371c0
Added "extras" to credentials for handling OIDC specific fields
rawnsley Oct 17, 2022
0455453
Simplifications to improve mergability
rawnsley Oct 17, 2022
c07d831
Truncate user ID differently when not an email
rawnsley Oct 19, 2022
5ac5835
Move from "Email" to "Account" in Verify UI
rawnsley Oct 19, 2022
ce6c71d
Use "sub" exclusively on client-side so there is consistency with ser…
rawnsley Oct 19, 2022
4c2c5bd
Show email login in OIDC mode with URL parameter
rawnsley Oct 20, 2022
194f1f7
Rationalise function parameters
rawnsley Oct 20, 2022
114d4cd
Origin parameter is mandatory, so provide a suitable default
rawnsley Oct 20, 2022
f2ca29a
Formatting for wait dialog
rawnsley Oct 20, 2022
cd48097
Removed stray nop changes
rawnsley Oct 21, 2022
6946ebd
Revert verification page name change to simplify PR
rawnsley Oct 24, 2022
dde99b8
Import order change to simplify PR
rawnsley Oct 24, 2022
1913de2
More verification name reversions
rawnsley Oct 24, 2022
7543c73
Reinstate verification window close from original spike
rawnsley Oct 24, 2022
3827569
Merge branch 'master' into avn-oidc-spike
rawnsley Oct 24, 2022
0eb0af6
Fixed undefined variable
rawnsley Oct 24, 2022
8bf274b
Merge branch 'master' into avn-oidc-spike
rawnsley Oct 28, 2022
21489b7
Merge branch 'master' into avn-oidc-spike
rawnsley Nov 8, 2022
7746e9d
Merge branch 'master' into avn-oidc-spike
rawnsley Nov 21, 2022
4a0c605
Merge branch 'master' into avn-oidc-spike
rawnsley Nov 29, 2022
a1364b5
Adjust indentation
rawnsley Nov 29, 2022
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
36 changes: 30 additions & 6 deletions admin/src/react-components/service-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ function isEmptyObject(obj) {
return true;
}

function getConfigurables(categorySchema) {
return getDescriptors(categorySchema).filter(
([path, descriptor]) =>
(qs.get("show_internal_configs") !== null || descriptor.internal !== "true") &&
(qs.get("show_deprecated_configs") !== null || descriptor.deprecated !== "true")
);
}

class ConfigurationEditor extends Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -226,6 +234,23 @@ class ConfigurationEditor extends Component {
);
}

renderListInput(path, descriptor, currentValue) {
const displayPath = path.join(" > ");
return (
<TextField
key={displayPath}
id={displayPath}
label={descriptor.name || displayPath}
value={((currentValue && Object.values(currentValue)) || []).join(",")}
onChange={ev => this.onChange(path, ev.target.value.split(",").map(v => v.trim()))}
helperText={descriptor.description}
type="text"
fullWidth
margin="normal"
/>
);
}

renderLongTextInput(path, descriptor, currentValue) {
const displayPath = path.join(" > ");

Expand Down Expand Up @@ -342,7 +367,7 @@ class ConfigurationEditor extends Component {
renderConfigurable(path, descriptor, currentValue) {
switch (descriptor.type) {
case "list":
return null;
return descriptor.of === "string" ? this.renderListInput(path, descriptor, currentValue) : null;
case "file":
return this.renderFileInput(path, descriptor, currentValue);
case "boolean":
Expand All @@ -360,10 +385,9 @@ class ConfigurationEditor extends Component {
}

renderTree(schema, category, config) {
const configurables = getDescriptors(schema[category])
.filter(([, descriptor]) => qs.get("show_internal_configs") !== null || descriptor.internal !== "true")
.filter(([, descriptor]) => qs.get("show_deprecated_configs") !== null || descriptor.deprecated !== "true")
.map(([path, descriptor]) => this.renderConfigurable(path, descriptor, getConfigValue(config, path)));
const configurables = getConfigurables(schema[category]).map(([path, descriptor]) =>
this.renderConfigurable(path, descriptor, getConfigValue(config, path))
);

return (
<form onSubmit={this.onSubmit.bind(this)}>
Expand Down Expand Up @@ -403,7 +427,7 @@ class ConfigurationEditor extends Component {
onChange={this.handleTabChange.bind(this)}
>
{schemaCategories
.filter(c => this.props.schema[c] && !isEmptyObject(this.props.schema[c]))
.filter(c => this.props.schema[c] && !isEmptyObject(getConfigurables(this.props.schema[c])))
.map(c => (
<Tab label={getCategoryDisplayName(c)} key={c} value={c} />
))}
Expand Down
10 changes: 6 additions & 4 deletions src/react-components/auth/AuthContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,13 @@ StorybookAuthContextProvider.propTypes = {

export function AuthContextProvider({ children, store }) {
const signIn = useCallback(
async email => {
async authPayload => {
const authChannel = new AuthChannel(store);
const socket = await connectToReticulum();
authChannel.setSocket(socket);
const { authComplete } = await authChannel.startAuthentication(email);
const { authComplete } = await (authPayload == "oidc"
? authChannel.startOIDCAuthentication()
: authChannel.startAuthentication(authPayload));
await authComplete;
await checkIsAdmin(socket, store);
},
Expand All @@ -86,14 +88,14 @@ export function AuthContextProvider({ children, store }) {
const authChannel = new AuthChannel(store);
const socket = await connectToReticulum();
authChannel.setSocket(socket);
await authChannel.verifyAuthentication(authParams.topic, authParams.token, authParams.payload);
await authChannel.verifyAuthentication(authParams.topic, authParams.token, authParams.payload, authParams.origin);
},
[store]
);

const signOut = useCallback(async () => {
configs.setIsAdmin(false);
store.update({ credentials: { token: null, email: null } });
store.update({ credentials: { token: null, email: null, extras: null } });
await store.resetToRandomDefaultAvatar();
}, [store]);

Expand Down
41 changes: 26 additions & 15 deletions src/react-components/auth/RoomSignInModalContainer.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import configs from "../../utils/configs";
import { SignInModal, SignInStep, SubmitEmail, WaitForVerification, SignInComplete } from "./SignInModal";
import { SignInModal, SignInStep, SubmitEmail, WaitForVerification, SignInComplete, SubmitOIDC } from "./SignInModal";
import { TERMS, PRIVACY } from "../../constants";

// TODO: Migrate to use AuthContext
export function RoomSignInModalContainer({ onClose, step, onSubmitEmail, message, onContinue }) {
export function RoomSignInModalContainer({ onClose, step, onSignIn, message, onContinue }) {
const [cachedEmail, setCachedEmail] = useState();

return (
<SignInModal onClose={onClose} closeable>
{step === SignInStep.submit && (
<SubmitEmail
onSubmitEmail={email => {
setCachedEmail(email);
onSubmitEmail(email);
}}
initialEmail={cachedEmail}
termsUrl={configs.link("terms_of_use", TERMS)}
showTerms={configs.feature("show_terms")}
privacyUrl={configs.link("privacy_notice", PRIVACY)}
showPrivacy={configs.feature("show_privacy")}
message={message}
/>
configs.APP_CONFIG.auth.use_oidc ? (
<SubmitOIDC
onSubmitOIDC={() => onSignIn("oidc")}
termsUrl={configs.link("terms_of_use", TERMS)}
showTerms={configs.feature("show_terms")}
privacyUrl={configs.link("privacy_notice", PRIVACY)}
showPrivacy={configs.feature("show_privacy")}
message={message}
/>
) : (
<SubmitEmail
onSubmitEmail={email => {
setCachedEmail(email);
onSignIn(email);
}}
initialEmail={cachedEmail}
termsUrl={configs.link("terms_of_use", TERMS)}
showTerms={configs.feature("show_terms")}
privacyUrl={configs.link("privacy_notice", PRIVACY)}
showPrivacy={configs.feature("show_privacy")}
message={message}
/>
)
)}
{step === SignInStep.waitForVerification && (
<WaitForVerification
Expand All @@ -38,7 +49,7 @@ export function RoomSignInModalContainer({ onClose, step, onSubmitEmail, message

RoomSignInModalContainer.propTypes = {
onClose: PropTypes.func,
onSubmitEmail: PropTypes.func,
onSignIn: PropTypes.func,
step: PropTypes.string,
message: PropTypes.object,
onContinue: PropTypes.func
Expand Down
59 changes: 51 additions & 8 deletions src/react-components/auth/SignInModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import PropTypes from "prop-types";
import { CloseButton } from "../input/CloseButton";
import { Modal } from "../modal/Modal";
import { FormattedMessage, useIntl, defineMessages } from "react-intl";
import { CancelButton, NextButton, ContinueButton } from "../input/Button";
import { CancelButton, NextButton, ContinueButton, Button } from "../input/Button";
import { TextInputField } from "../input/TextInputField";
import { Column } from "../layout/Column";
import { LegalMessage } from "./LegalMessage";
import configs from "../../utils/configs";

export const SignInStep = {
submit: "submit",
Expand Down Expand Up @@ -136,15 +137,57 @@ SubmitEmail.propTypes = {
onSubmitEmail: PropTypes.func.isRequired
};

export function SubmitOIDC({ onSubmitOIDC, privacyUrl, termsUrl, message }) {
const intl = useIntl();

const onSubmitForm = useCallback(
e => {
e.preventDefault();
onSubmitOIDC();
},
[onSubmitOIDC]
);

return (
<Column center padding as="form" onSubmit={onSubmitForm}>
<p>
{message ? (
intl.formatMessage(message)
) : (
<FormattedMessage id="sign-in-modal.prompt" defaultMessage="Please Sign In" />
)}
</p>
<p>
<small>
<LegalMessage termsUrl={termsUrl} privacyUrl={privacyUrl} />
</small>
</p>
<Button preset="accept" type="submit">{configs.APP_CONFIG.auth.oidc_button_label || "Sign In With SSO"}</Button>
</Column>
);
}
SubmitOIDC.propTypes = {
onSubmitOIDC: PropTypes.func.isRequired,
message: PropTypes.object,
termsUrl: PropTypes.string,
privacyUrl: PropTypes.string
};

export function WaitForVerification({ email, onCancel, showNewsletterSignup }) {
return (
<Column center padding>
<FormattedMessage
id="sign-in-modal.wait-for-verification"
defaultMessage="<p>Email sent to {email}!</p><p>To continue, click on the link in the email using your phone, tablet, or PC.</p><p>No email? You may not be able to create an account.</p>"
// eslint-disable-next-line react/display-name
values={{ email, p: chunks => <p>{chunks}</p> }}
/>
{email ? (
<FormattedMessage
id="sign-in-modal.wait-for-verification"
defaultMessage="<p>Email sent to {email}!</p><p>To continue, click on the link in the email using your phone, tablet, or PC.</p><p>No email? You may not be able to create an account.</p>"
// eslint-disable-next-line react/display-name
values={{ email, p: chunks => <p>{chunks}</p> }}
/>
) : (
<p>
<FormattedMessage id="sign-in.oidc-auth-started" defaultMessage="Waiting for signin..."/>
</p>
)}
{showNewsletterSignup && (
<p>
<small>
Expand All @@ -166,7 +209,7 @@ export function WaitForVerification({ email, onCancel, showNewsletterSignup }) {

WaitForVerification.propTypes = {
showNewsletterSignup: PropTypes.bool,
email: PropTypes.string.isRequired,
email: PropTypes.string,
onCancel: PropTypes.func.isRequired
};

Expand Down
50 changes: 39 additions & 11 deletions src/react-components/auth/SignInModalContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React, { useCallback, useReducer, useContext, useEffect } from "react";
import { TERMS, PRIVACY } from "../../constants";
import configs from "../../utils/configs";
import { AuthContext } from "./AuthContext";
import { SignInModal, SignInStep, WaitForVerification, SubmitEmail } from "./SignInModal";
import { SignInModal, SignInStep, WaitForVerification, SubmitEmail, SubmitOIDC } from "./SignInModal";
import { Column } from "../layout/Column";

const SignInAction = {
submitEmail: "submitEmail",
submitOIDC: "submitOIDC",
verificationReceived: "verificationReceived",
cancel: "cancel"
};
Expand All @@ -19,6 +21,8 @@ function loginReducer(state, action) {
switch (action.type) {
case SignInAction.submitEmail:
return { step: SignInStep.waitForVerification, email: action.email };
case SignInAction.submitOIDC:
return { step: SignInStep.waitForVerification };
case SignInAction.verificationReceived:
return { ...state, step: SignInStep.complete };
case SignInAction.cancel:
Expand All @@ -40,6 +44,16 @@ function useSignIn() {
[auth]
);

const submitOIDC = useCallback(
() => {
auth.signIn("oidc").then(() => {
dispatch({ type: SignInAction.verificationReceived });
});
dispatch({ type: SignInAction.submitOIDC });
},
[auth]
);

const cancel = useCallback(() => {
dispatch({ type: SignInAction.cancel });
}, []);
Expand All @@ -48,13 +62,15 @@ function useSignIn() {
step: state.step,
email: state.email,
submitEmail,
submitOIDC,
cancel
};
}


export function SignInModalContainer() {
const qs = new URLSearchParams(location.search);
const { step, submitEmail, cancel, email } = useSignIn();
const { step, submitEmail, submitOIDC, cancel, email } = useSignIn();
const redirectUrl = qs.get("sign_in_destination_url") || "/";

useEffect(() => {
Expand All @@ -66,15 +82,27 @@ export function SignInModalContainer() {
return (
<SignInModal disableFullscreen>
{step === SignInStep.submit ? (
<SubmitEmail
onSubmitEmail={submitEmail}
initialEmail={email}
signInReason={qs.get("sign_in_reason")}
termsUrl={configs.link("terms_of_use", TERMS)}
showTerms={configs.feature("show_terms")}
privacyUrl={configs.link("privacy_notice", PRIVACY)}
showPrivacy={configs.feature("show_privacy")}
/>
<Column center padding>
{configs.APP_CONFIG.auth.use_oidc &&
<SubmitOIDC
onSubmitOIDC={submitOIDC}
termsUrl={configs.link("terms_of_use", TERMS)}
showTerms={configs.feature("show_terms")}
privacyUrl={configs.link("privacy_notice", PRIVACY)}
showPrivacy={configs.feature("show_privacy")}
/>
}
{(!configs.APP_CONFIG.auth.use_oidc || qs.has("show_email_signin")) && (
<SubmitEmail
onSubmitEmail={submitEmail}
initialEmail={email}
termsUrl={configs.link("terms_of_use", TERMS)}
showTerms={configs.feature("show_terms")}
privacyUrl={configs.link("privacy_notice", PRIVACY)}
showPrivacy={configs.feature("show_privacy")}
/>
)}
</Column>
) : (
<WaitForVerification
onCancel={cancel}
Expand Down
6 changes: 5 additions & 1 deletion src/react-components/auth/VerifyModal.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import { Spinner } from "../misc/Spinner";
import { Modal } from "../modal/Modal";
Expand All @@ -24,6 +24,10 @@ export function VerifyingEmail() {
}

export function EmailVerified({ origin }) {
// Close the window, but display a comfort message anyway
useEffect(function() {
window.close();
});
return (
<Column center padding grow>
<b>
Expand Down
Loading