mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 09:52:49 +08:00
@@ -74,8 +74,8 @@ export function AuthModal() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'oauth': {
|
case 'oauth': {
|
||||||
const { code, state } = payload;
|
const { code, state, provider } = payload;
|
||||||
await authService.signInOauth(code, state);
|
await authService.signInOauth(code, state, provider);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { notify } from '@affine/component';
|
|||||||
import { AuthInput, ModalHeader } from '@affine/component/auth-components';
|
import { AuthInput, ModalHeader } from '@affine/component/auth-components';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
import { track } from '@affine/core/mixpanel';
|
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { ArrowRightBigIcon } from '@blocksuite/icons/rc';
|
import { ArrowRightBigIcon } from '@blocksuite/icons/rc';
|
||||||
import { useService } from '@toeverything/infra';
|
import { useService } from '@toeverything/infra';
|
||||||
@@ -59,7 +58,6 @@ export const SignIn: FC<AuthPanelProps<'signIn'>> = ({
|
|||||||
email,
|
email,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
track.$.$.auth.signIn();
|
|
||||||
await authService.sendEmailMagicLink(email, verifyToken, challenge);
|
await authService.sendEmailMagicLink(email, verifyToken, challenge);
|
||||||
setAuthState({
|
setAuthState({
|
||||||
state: 'afterSignInSendEmail',
|
state: 'afterSignInSendEmail',
|
||||||
@@ -68,7 +66,6 @@ export const SignIn: FC<AuthPanelProps<'signIn'>> = ({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await authService.sendEmailMagicLink(email, verifyToken, challenge);
|
await authService.sendEmailMagicLink(email, verifyToken, challenge);
|
||||||
track.$.$.auth.signUp();
|
|
||||||
setAuthState({
|
setAuthState({
|
||||||
state: 'afterSignUpSendEmail',
|
state: 'afterSignUpSendEmail',
|
||||||
email,
|
email,
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ type ShareEvents =
|
|||||||
| 'copyShareLink'
|
| 'copyShareLink'
|
||||||
| 'openShareMenu'
|
| 'openShareMenu'
|
||||||
| 'share';
|
| 'share';
|
||||||
type AuthEvents = 'signIn' | 'signUp' | 'oauth' | 'signOut';
|
type AuthEvents = 'signIn' | 'signInFail' | 'signedIn' | 'signOut';
|
||||||
type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName';
|
type AccountEvents = 'uploadAvatar' | 'removeAvatar' | 'updateUserName';
|
||||||
type PaymentEvents =
|
type PaymentEvents =
|
||||||
| 'viewPlans'
|
| 'viewPlans'
|
||||||
@@ -141,7 +141,7 @@ const PageEvents = {
|
|||||||
$: {
|
$: {
|
||||||
$: {
|
$: {
|
||||||
$: ['createWorkspace', 'checkout'],
|
$: ['createWorkspace', 'checkout'],
|
||||||
auth: ['oauth', 'signIn', 'signUp'],
|
auth: ['signIn', 'signedIn', 'signInFail', 'signOut'],
|
||||||
},
|
},
|
||||||
sharePanel: {
|
sharePanel: {
|
||||||
$: ['createShareLink', 'copyShareLink', 'export', 'open'],
|
$: ['createShareLink', 'copyShareLink', 'export', 'open'],
|
||||||
@@ -347,9 +347,16 @@ type TabActionType =
|
|||||||
| 'switchTab'
|
| 'switchTab'
|
||||||
| 'separateTabs';
|
| 'separateTabs';
|
||||||
|
|
||||||
|
type AuthArgs = {
|
||||||
|
method: 'password' | 'magic-link' | 'oauth';
|
||||||
|
provider?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type EventArgs = {
|
export type EventArgs = {
|
||||||
createWorkspace: { flavour: string };
|
createWorkspace: { flavour: string };
|
||||||
oauth: { provider: string };
|
signIn: AuthArgs;
|
||||||
|
signedIn: AuthArgs;
|
||||||
|
signInFail: AuthArgs;
|
||||||
viewPlans: PaymentEventArgs;
|
viewPlans: PaymentEventArgs;
|
||||||
checkout: PaymentEventArgs;
|
checkout: PaymentEventArgs;
|
||||||
subscribe: PaymentEventArgs;
|
subscribe: PaymentEventArgs;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||||
|
import { track } from '@affine/core/mixpanel';
|
||||||
import { appInfo } from '@affine/electron-api';
|
import { appInfo } from '@affine/electron-api';
|
||||||
import type { OAuthProviderType } from '@affine/graphql';
|
import type { OAuthProviderType } from '@affine/graphql';
|
||||||
import {
|
import {
|
||||||
@@ -82,32 +83,41 @@ export class AuthService extends Service {
|
|||||||
verifyToken: string,
|
verifyToken: string,
|
||||||
challenge?: string
|
challenge?: string
|
||||||
) {
|
) {
|
||||||
const res = await this.fetchService.fetch('/api/auth/sign-in', {
|
track.$.$.auth.signIn({ method: 'magic-link' });
|
||||||
method: 'POST',
|
try {
|
||||||
body: JSON.stringify({
|
await this.fetchService.fetch('/api/auth/sign-in', {
|
||||||
email,
|
method: 'POST',
|
||||||
// we call it [callbackUrl] instead of [redirect_uri]
|
body: JSON.stringify({
|
||||||
// to make it clear the url is used to finish the sign-in process instead of redirect after signed-in
|
email,
|
||||||
callbackUrl: `/magic-link?client=${environment.isElectron ? appInfo?.schema : 'web'}`,
|
// we call it [callbackUrl] instead of [redirect_uri]
|
||||||
}),
|
// to make it clear the url is used to finish the sign-in process instead of redirect after signed-in
|
||||||
headers: {
|
callbackUrl: `/magic-link?client=${environment.isElectron ? appInfo?.schema : 'web'}`,
|
||||||
'content-type': 'application/json',
|
}),
|
||||||
...this.captchaHeaders(verifyToken, challenge),
|
headers: {
|
||||||
},
|
'content-type': 'application/json',
|
||||||
});
|
...this.captchaHeaders(verifyToken, challenge),
|
||||||
if (!res.ok) {
|
},
|
||||||
throw new Error('Failed to send email');
|
});
|
||||||
|
} catch (e) {
|
||||||
|
track.$.$.auth.signInFail({ method: 'magic-link' });
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signInMagicLink(email: string, token: string) {
|
async signInMagicLink(email: string, token: string) {
|
||||||
await this.fetchService.fetch('/api/auth/magic-link', {
|
try {
|
||||||
method: 'POST',
|
await this.fetchService.fetch('/api/auth/magic-link', {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
},
|
'Content-Type': 'application/json',
|
||||||
body: JSON.stringify({ email, token }),
|
},
|
||||||
});
|
body: JSON.stringify({ email, token }),
|
||||||
|
});
|
||||||
|
track.$.$.auth.signedIn({ method: 'magic-link' });
|
||||||
|
} catch (e) {
|
||||||
|
track.$.$.auth.signInFail({ method: 'magic-link' });
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async oauthPreflight(
|
async oauthPreflight(
|
||||||
@@ -115,41 +125,54 @@ export class AuthService extends Service {
|
|||||||
client: string,
|
client: string,
|
||||||
/** @deprecated*/ redirectUrl?: string
|
/** @deprecated*/ redirectUrl?: string
|
||||||
) {
|
) {
|
||||||
const res = await this.fetchService.fetch('/api/oauth/preflight', {
|
track.$.$.auth.signIn({ method: 'oauth', provider });
|
||||||
method: 'POST',
|
try {
|
||||||
body: JSON.stringify({ provider, redirect_uri: redirectUrl }),
|
const res = await this.fetchService.fetch('/api/oauth/preflight', {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'content-type': 'application/json',
|
body: JSON.stringify({ provider, redirect_uri: redirectUrl }),
|
||||||
},
|
headers: {
|
||||||
});
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let { url } = await res.json();
|
let { url } = await res.json();
|
||||||
|
|
||||||
// change `state=xxx` to `state={state:xxx,native:true}`
|
// change `state=xxx` to `state={state:xxx,native:true}`
|
||||||
// so we could know the callback should be redirect to native app
|
// so we could know the callback should be redirect to native app
|
||||||
const oauthUrl = new URL(url);
|
const oauthUrl = new URL(url);
|
||||||
oauthUrl.searchParams.set(
|
oauthUrl.searchParams.set(
|
||||||
'state',
|
'state',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
state: oauthUrl.searchParams.get('state'),
|
state: oauthUrl.searchParams.get('state'),
|
||||||
client,
|
client,
|
||||||
})
|
provider,
|
||||||
);
|
})
|
||||||
url = oauthUrl.toString();
|
);
|
||||||
|
url = oauthUrl.toString();
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
|
} catch (e) {
|
||||||
|
track.$.$.auth.signInFail({ method: 'oauth', provider });
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signInOauth(code: string, state: string) {
|
async signInOauth(code: string, state: string, provider: string) {
|
||||||
const res = await this.fetchService.fetch('/api/oauth/callback', {
|
try {
|
||||||
method: 'POST',
|
const res = await this.fetchService.fetch('/api/oauth/callback', {
|
||||||
body: JSON.stringify({ code, state }),
|
method: 'POST',
|
||||||
headers: {
|
body: JSON.stringify({ code, state }),
|
||||||
'content-type': 'application/json',
|
headers: {
|
||||||
},
|
'content-type': 'application/json',
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return await res.json();
|
track.$.$.auth.signedIn({ method: 'oauth', provider });
|
||||||
|
return res.json();
|
||||||
|
} catch (e) {
|
||||||
|
track.$.$.auth.signInFail({ method: 'oauth', provider });
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signInPassword(credential: {
|
async signInPassword(credential: {
|
||||||
@@ -158,18 +181,25 @@ export class AuthService extends Service {
|
|||||||
verifyToken: string;
|
verifyToken: string;
|
||||||
challenge?: string;
|
challenge?: string;
|
||||||
}) {
|
}) {
|
||||||
const res = await this.fetchService.fetch('/api/auth/sign-in', {
|
track.$.$.auth.signIn({ method: 'password' });
|
||||||
method: 'POST',
|
try {
|
||||||
body: JSON.stringify(credential),
|
const res = await this.fetchService.fetch('/api/auth/sign-in', {
|
||||||
headers: {
|
method: 'POST',
|
||||||
'content-type': 'application/json',
|
body: JSON.stringify(credential),
|
||||||
...this.captchaHeaders(credential.verifyToken, credential.challenge),
|
headers: {
|
||||||
},
|
'content-type': 'application/json',
|
||||||
});
|
...this.captchaHeaders(credential.verifyToken, credential.challenge),
|
||||||
if (!res.ok) {
|
},
|
||||||
throw new Error('Failed to sign in');
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error('Failed to sign in');
|
||||||
|
}
|
||||||
|
this.session.revalidate();
|
||||||
|
track.$.$.auth.signedIn({ method: 'password' });
|
||||||
|
} catch (e) {
|
||||||
|
track.$.$.auth.signInFail({ method: 'password' });
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
this.session.revalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async signOut() {
|
async signOut() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { supportedClient } from './common';
|
|||||||
interface LoaderData {
|
interface LoaderData {
|
||||||
state: string;
|
state: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
provider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loader: LoaderFunction = async ({ request }) => {
|
export const loader: LoaderFunction = async ({ request }) => {
|
||||||
@@ -27,12 +28,13 @@ export const loader: LoaderFunction = async ({ request }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { state, client } = JSON.parse(stateStr);
|
const { state, client, provider } = JSON.parse(stateStr);
|
||||||
stateStr = state;
|
stateStr = state;
|
||||||
|
|
||||||
const payload: LoaderData = {
|
const payload: LoaderData = {
|
||||||
state,
|
state,
|
||||||
code,
|
code,
|
||||||
|
provider,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!client || client === 'web') {
|
if (!client || client === 'web') {
|
||||||
@@ -64,7 +66,7 @@ export const Component = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
auth
|
auth
|
||||||
.signInOauth(data.code, data.state)
|
.signInOauth(data.code, data.state, data.provider)
|
||||||
.then(({ redirectUri }) => {
|
.then(({ redirectUri }) => {
|
||||||
// TODO(@forehalo): need a good way to go back to previous tab and close current one
|
// TODO(@forehalo): need a good way to go back to previous tab and close current one
|
||||||
nav(redirectUri ?? '/');
|
nav(redirectUri ?? '/');
|
||||||
|
|||||||
Reference in New Issue
Block a user