Skip to content

Opening this for comparison and review, NOT READY FOR MERGE #4779

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

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3c06be0
added a prune script to fix dep issues
immber Feb 10, 2025
c8442ea
intial commit- generator issues
immber Feb 11, 2025
bb039ef
borked admin/routes Configure & Login, redo
immber Feb 11, 2025
0324dbf
clean build after fixing fixtures
immber Feb 11, 2025
503009d
fixed imports maybe
immber Feb 12, 2025
b351ac5
btn color change
immber Feb 12, 2025
c03ec68
added atproto authenticator and callAuthorize w/ cookiestore for stat…
immber Mar 10, 2025
eb1a45f
first attempt at a callback
immber Mar 11, 2025
7814e14
first attempt at a callback, jk
immber Mar 11, 2025
a6b9165
added handlers, fixed Response compile issue
immber Mar 12, 2025
9ea45f4
identity errors, brkn client-metadata, and separated classes
immber Mar 17, 2025
e6728df
route handlers work, but no authenticator in tenant cache and signal …
immber Mar 20, 2025
ed3dec7
fixed tenant caching, authorize still borked
immber Mar 31, 2025
a0b4419
updated btn colors
immber Mar 31, 2025
3d516d4
added handle input, bskybtn now onSubmit w/ form
immber Apr 1, 2025
b3ba183
converted get btn > post forms w errs,added callback handler
immber Apr 2, 2025
029a612
updated lint & language for bsky config
immber Apr 4, 2025
ee63cb3
make like forgotpwd post req, use jsonMiddleware & rest
immber Apr 4, 2025
6c04e4e
rename redirectURL to authURL, srvr errs surface to cli , cors err
immber Apr 4, 2025
34a071e
set clientID to clientName & add clientSecret before try mutation
immber Apr 5, 2025
3a92913
passing handle on all 3 form containers, network err missing
immber Apr 7, 2025
43239a0
messed w surfacing srv errs to client form, no love
immber Apr 8, 2025
34b0b9e
srv errs surface to cli again
immber Apr 8, 2025
c213429
dos probs,1 redirecting to err msg,2 no cookies
immber Apr 8, 2025
22c9129
remove node-fetch from server
nick-funk Apr 23, 2025
c39d19c
Merge branch 'develop' into proposal/remove-node-fetch
nick-funk Apr 23, 2025
4aaf0dc
working on fetch errs
immber Apr 23, 2025
dad7d6c
merge remove node fetch
immber Apr 23, 2025
d7aeb56
handler no redirect, bsky session has been del bug, fixed fetch and c…
immber Apr 24, 2025
bf8317c
fixed redirect, all 3 forms work, but hit session deleted bug
immber Apr 29, 2025
efa8237
made the session agent reusable, still needs refresh
immber Apr 30, 2025
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
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"license": "Apache-2.0",
"dependencies": {
"@ampproject/toolbox-cache-url": "^2.9.0",
"@atproto/syntax": "^0.3.3",
"@coralproject/bunyan-prettystream": "^0.1.4",
"@fluent/bundle": "^0.15.1",
"@fluent/dom": "^0.7.0",
Expand Down
7 changes: 7 additions & 0 deletions client/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const AuthConfigContainer: FunctionComponent<Props> = ({
const integrations = [
data.auth.integrations.google,
data.auth.integrations.facebook,
data.auth.integrations.bsky,
data.auth.integrations.sso,
data.auth.integrations.local,
data.auth.integrations.oidc,
Expand Down Expand Up @@ -122,6 +123,7 @@ const enhanced = withFragmentContainer<Props>({
`,
auth: graphql`
fragment AuthConfigContainer_auth on Auth {
...BskyConfig_formValues @relay(mask:false)
...FacebookConfig_formValues @relay(mask: false)
...GoogleConfig_formValues @relay(mask: false)
...SSOConfig_formValues @relay(mask: false)
Expand All @@ -130,6 +132,7 @@ const enhanced = withFragmentContainer<Props>({
...SessionConfig_formValues @relay(mask: false)

...FacebookConfigContainer_auth
...BskyConfigContainer_auth
...GoogleConfigContainer_auth
...SSOConfigContainer_auth
...OIDCConfigContainer_auth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { FunctionComponent } from "react";
import { PropTypesOf } from "coral-framework/types";
import { HorizontalGutter } from "coral-ui/components/v2";

import BskyConfigContainer from "./BskyConfigContainer";
import FacebookConfigContainer from "./FacebookConfigContainer";
import GoogleConfigContainer from "./GoogleConfigContainer";
import LocalAuthConfigContainer from "./LocalAuthConfigContainer";
Expand All @@ -11,7 +12,8 @@ import SSOConfigContainer from "./SSOConfigContainer";

interface Props {
disabled?: boolean;
auth: PropTypesOf<typeof FacebookConfigContainer>["auth"] &
auth: PropTypesOf<typeof BskyConfigContainer>["auth"] &
PropTypesOf<typeof FacebookConfigContainer>["auth"] &
PropTypesOf<typeof GoogleConfigContainer>["auth"] &
PropTypesOf<typeof SSOConfigContainer>["auth"] &
PropTypesOf<typeof OIDCConfigContainer>["auth"];
Expand All @@ -27,6 +29,7 @@ const AuthIntegrationsConfig: FunctionComponent<Props> = ({
<SSOConfigContainer disabled={disabled} auth={auth} />
<GoogleConfigContainer disabled={disabled} auth={auth} />
<FacebookConfigContainer disabled={disabled} auth={auth} />
<BskyConfigContainer disabled={disabled} auth={auth} />
</HorizontalGutter>
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Localized } from "@fluent/react/compat";
import React, { FunctionComponent } from "react";
import { graphql } from "react-relay";

import {
Condition,
required,
validateWhen,
} from "coral-framework/lib/validation";
import { FormFieldDescription, TextLink } from "coral-ui/components/v2";

import Header from "../../Header";
import HorizontalRule from "../../HorizontalRule";
import ClientIDField from "./ClientIDField";
import ClientSecretField from "./ClientSecretField";
import ConfigBoxWithToggleField from "./ConfigBoxWithToggleField";
import RedirectField from "./RedirectField";
import RegistrationField from "./RegistrationField";
import TargetFilterField from "./TargetFilterField";

// eslint-disable-next-line no-unused-expressions
graphql`
fragment BskyConfig_formValues on Auth {
integrations {
bsky {
enabled
allowRegistration
targetFilter {
admin
stream
}
clientID
clientSecret
}
}
}
`;

interface Props {
disabled?: boolean;
callbackURL: string;
}

const BskyLink = () => (
<TextLink target="_blank">
{"https://docs.bsky.app/docs/advanced-guides/oauth-client"}
</TextLink>
);

const isEnabled: Condition = (value, values) =>
Boolean(values.auth.integrations.bsky.enabled);

const BskyConfig: FunctionComponent<Props> = ({ disabled, callbackURL }) => (
<ConfigBoxWithToggleField
data-testid="configure-auth-bsky-container"
title={
<Localized id="configure-auth-bsky-loginWith">
<Header container="h2">Login with Bluesky</Header>
</Localized>
}
name="auth.integrations.bsky.enabled"
disabled={disabled}
>
{(disabledInside) => (
<>
<Localized
id="configure-auth-bsky-toEnableIntegration"
elems={{ Link: <BskyLink />, br: <br /> }}
>
<FormFieldDescription>
Client ID is the name that will identify this ATproto oauth client
to users. For more information visit:
<br />
{<BskyLink />}
</FormFieldDescription>
</Localized>
<RedirectField url={callbackURL} />
<HorizontalRule />
<ClientIDField
name="auth.integrations.bsky.clientID"
validate={validateWhen(isEnabled, required)}
disabled={disabledInside}
/>
<ClientSecretField
name="auth.integrations.bsky.clientSecret"
validate={validateWhen(isEnabled, required)}
disabled={disabledInside}
/>
<TargetFilterField
label={
<Localized id="configure-auth-bsky-useLoginOn">
<span>Use Bluesky login on</span>
</Localized>
}
name="auth.integrations.bsky.targetFilter"
disabled={disabledInside}
/>
<RegistrationField
name="auth.integrations.bsky.allowRegistration"
disabled={disabledInside}
/>
</>
)}
</ConfigBoxWithToggleField>
);

export default BskyConfig;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import { graphql } from "react-relay";

import { withFragmentContainer } from "coral-framework/lib/relay";

import { BskyConfigContainer_auth as AuthData } from "coral-admin/__generated__/BskyConfigContainer_auth.graphql"

import BskyConfig from "./BskyConfig";

interface Props {
auth: AuthData;
disabled?: boolean;
}

const BskyConfigContainer: React.FunctionComponent<Props> = ({
disabled,
auth,
}) => {
return (
<BskyConfig
disabled={disabled}
callbackURL={auth.integrations.bsky.callbackURL}
/>
);
};

const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment BskyConfigContainer_auth on Auth {
integrations {
bsky {
callbackURL
}
}
}
`,
})(BskyConfigContainer);

export default enhanced;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PropTypesOf } from "coral-framework/types";
import { CallOut, HorizontalGutter } from "coral-ui/components/v2";

import OrSeparator from "./OrSeparator";
import SignInWithBskyContainer from "./SignInWithBskyContainer";
import SignInWithEmailContainer from "./SignInWithEmailContainer";
import SignInWithFacebookContainer from "./SignInWithFacebookContainer";
import SignInWithGoogleContainer from "./SignInWithGoogleContainer";
Expand All @@ -17,24 +18,27 @@ import styles from "./SignIn.css";
interface Props {
error: string | null;
emailEnabled?: boolean;
bskyEnabled?: boolean;
facebookEnabled?: boolean;
googleEnabled?: boolean;
oidcEnabled?: boolean;
auth: PropTypesOf<typeof SignInWithOIDCContainer>["auth"] &
PropTypesOf<typeof SignInWithBskyContainer>["auth"] &
PropTypesOf<typeof SignInWithFacebookContainer>["auth"] &
PropTypesOf<typeof SignInWithGoogleContainer>["auth"];
}

const SignIn: FunctionComponent<Props> = ({
emailEnabled,
bskyEnabled,
facebookEnabled,
googleEnabled,
oidcEnabled,
auth,
error,
}) => {
const oneClickIntegrationEnabled =
facebookEnabled || googleEnabled || oidcEnabled;
bskyEnabled || facebookEnabled || googleEnabled || oidcEnabled;
return (
<>
<AuthBox
Expand All @@ -60,6 +64,11 @@ const SignIn: FunctionComponent<Props> = ({
)}
{oneClickIntegrationEnabled && (
<HorizontalGutter>
{bskyEnabled && (
<div className={styles.loginButton}>
<SignInWithBskyContainer auth={auth} />
</div>
)}
{facebookEnabled && (
<div className={styles.loginButton}>
<SignInWithFacebookContainer auth={auth} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ const SignInContainer: FunctionComponent<Props> = ({ auth }) => {
emailEnabled={
integrations.local.enabled && integrations.local.targetFilter.admin
}
bskyEnabled={
integrations.bsky.enabled &&
integrations.bsky.targetFilter.admin
}
facebookEnabled={
integrations.facebook.enabled &&
integrations.facebook.targetFilter.admin
Expand All @@ -58,13 +62,20 @@ const enhanced = withFragmentContainer<Props>({
...SignInWithOIDCContainer_auth
...SignInWithGoogleContainer_auth
...SignInWithFacebookContainer_auth
...SignInWithBskyContainer_auth
integrations {
local {
enabled
targetFilter {
admin
}
}
bsky {
enabled
targetFilter {
admin
}
}
facebook {
enabled
targetFilter {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { isValidHandle } from "@atproto/syntax";
import { SignInWithBskyContainer_auth as AuthData } from "coral-admin/__generated__/SignInWithBskyContainer_auth.graphql";
import { REDIRECT_TO_PARAM } from "coral-common/common/lib/constants";
import SignInWithBskyForm, {
SignInWithBsky,
} from "coral-framework/components/BskyLoginForm";
import { useCoralContext } from "coral-framework/lib/bootstrap";
import { withFragmentContainer } from "coral-framework/lib/relay";
import { BskyHandleInput, postBskyApiAuth } from "coral-framework/rest";
import { FORM_ERROR } from "final-form";
import qs from "querystringify";
import React, { FormEvent, FunctionComponent, useCallback } from "react";
import { graphql } from "react-relay";

interface Props {
auth: AuthData;
}

const SignInWithBskyContainer: FunctionComponent<Props> = ({ auth }) => {
const { window } = useCoralContext();
// grab user origin so we can send back when done with auth
const redirectTo = window.location.pathname;
// get /api/auth/bsky route for tenant's bsky integration
const authPath = `${auth.integrations.bsky.authURL}?${qs.stringify({
[REDIRECT_TO_PARAM]: redirectTo,
})}`;
const onSubmit: SignInWithBsky["onSubmit"] = useCallback(
async (input, form) => {
try {
// catch invalid handle early before redirecting to /api/auth/bsky
const handle = input.handle;
const validHandle = isValidHandle(handle as string);
if (validHandle) {
await postBskyApiAuth(input as BskyHandleInput, authPath);
return;
} else {
return { [FORM_ERROR]: "Invalid handle" };
}
} catch (error) {
return { [FORM_ERROR]: error.message };
}
},
[authPath]
);
return <SignInWithBskyForm onSubmit={onSubmit} />;
};

const enhanced = withFragmentContainer<Props>({
auth: graphql`
fragment SignInWithBskyContainer_auth on Auth {
integrations {
bsky {
authURL
}
}
}
`,
})(SignInWithBskyContainer);

export default enhanced;
Loading