mirror of
https://github.com/wassname/talk.git
synced 2026-08-20 12:50:41 +08:00
[next] Admin Authentication (#2155)
* feat: Support sign in via E-Mail, and social logins + permission check * feat: no permission info * test: unit test * test: fix remaining tests * test: add integration tests * feat: add admin account completion * test: auth completion feature tests * fix: translation * fix: comment marker design * fix: linting issues * chore: address pr review comments * chore: add comment
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer";
|
||||
|
||||
export type ClearAuthErrorMutation = () => Promise<void>;
|
||||
|
||||
export async function commit(environment: Environment, input: undefined) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(null, "authError");
|
||||
});
|
||||
}
|
||||
|
||||
export const withClearAuthErrorMutation = createMutationContainer(
|
||||
"clearAuthError",
|
||||
commit
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation";
|
||||
|
||||
interface CompleteAccountInput {
|
||||
authToken: string;
|
||||
}
|
||||
export type CompleteAccountMutation = (
|
||||
input: CompleteAccountInput
|
||||
) => Promise<void>;
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: CompleteAccountInput,
|
||||
context: TalkContext
|
||||
) {
|
||||
await setAuthToken(environment, { authToken: input.authToken }, context);
|
||||
}
|
||||
|
||||
export const withCompleteAccountMutation = createMutationContainer(
|
||||
"completeAccount",
|
||||
commit
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Environment, RecordSource } from "relay-runtime";
|
||||
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { createRelayEnvironment } from "talk-framework/testHelpers";
|
||||
|
||||
import { commit } from "./SetAuthViewMutation";
|
||||
|
||||
let environment: Environment;
|
||||
const source: RecordSource = new RecordSource();
|
||||
|
||||
beforeAll(() => {
|
||||
environment = createRelayEnvironment({
|
||||
source,
|
||||
});
|
||||
});
|
||||
|
||||
it("Sets view", () => {
|
||||
const view = "SIGN_IN";
|
||||
commit(environment, { view }, {} as any);
|
||||
expect(source.get(LOCAL_ID)!.authView).toEqual(view);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer";
|
||||
|
||||
export interface SetAuthViewInput {
|
||||
// TODO: replace with generated typescript types.
|
||||
view: "SIGN_IN" | "ADD_EMAIL_ADDRESS" | "CREATE_USERNAME" | "CREATE_PASSWORD";
|
||||
}
|
||||
|
||||
export type SetAuthViewMutation = (input: SetAuthViewInput) => Promise<void>;
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: SetAuthViewInput,
|
||||
{ pym }: TalkContext
|
||||
) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(input.view, "authView");
|
||||
});
|
||||
}
|
||||
|
||||
export const withSetAuthViewMutation = createMutationContainer(
|
||||
"setAuthView",
|
||||
commit
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutationContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
import { Omit } from "talk-framework/types";
|
||||
|
||||
import { SetEmailMutation as MutationTypes } from "talk-admin/__generated__/SetEmailMutation.graphql";
|
||||
|
||||
export type SetEmailInput = Omit<
|
||||
MutationTypes["variables"]["input"],
|
||||
"clientMutationId"
|
||||
>;
|
||||
|
||||
const mutation = graphql`
|
||||
mutation SetEmailMutation($input: SetEmailInput!) {
|
||||
setEmail(input: $input) {
|
||||
user {
|
||||
email
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let clientMutationId = 0;
|
||||
|
||||
function commit(environment: Environment, input: SetEmailInput) {
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
clientMutationId: (clientMutationId++).toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const withSetEmailMutation = createMutationContainer("setEmail", commit);
|
||||
|
||||
export type SetEmailMutation = (
|
||||
input: SetEmailInput
|
||||
) => Promise<MutationTypes["response"]["setEmail"]>;
|
||||
@@ -0,0 +1,51 @@
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutationContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
import { Omit } from "talk-framework/types";
|
||||
|
||||
import { SetPasswordMutation as MutationTypes } from "talk-admin/__generated__/SetPasswordMutation.graphql";
|
||||
|
||||
export type SetPasswordInput = Omit<
|
||||
MutationTypes["variables"]["input"],
|
||||
"clientMutationId"
|
||||
>;
|
||||
|
||||
const mutation = graphql`
|
||||
mutation SetPasswordMutation($input: SetPasswordInput!) {
|
||||
setPassword(input: $input) {
|
||||
user {
|
||||
profiles {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let clientMutationId = 0;
|
||||
|
||||
function commit(environment: Environment, input: SetPasswordInput) {
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
clientMutationId: (clientMutationId++).toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const withSetPasswordMutation = createMutationContainer(
|
||||
"setPassword",
|
||||
commit
|
||||
);
|
||||
|
||||
export type SetPasswordMutation = (
|
||||
input: SetPasswordInput
|
||||
) => Promise<MutationTypes["response"]["setPassword"]>;
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Environment, RecordSource } from "relay-runtime";
|
||||
|
||||
import { REDIRECT_PATH_KEY } from "talk-admin/constants";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay";
|
||||
import { createRelayEnvironment } from "talk-framework/testHelpers";
|
||||
|
||||
import {
|
||||
createInMemoryStorage,
|
||||
createPromisifiedStorage,
|
||||
} from "talk-framework/lib/storage";
|
||||
import { commit } from "./SetRedirectPathMutation";
|
||||
|
||||
let environment: Environment;
|
||||
@@ -14,7 +19,20 @@ beforeAll(() => {
|
||||
});
|
||||
});
|
||||
|
||||
it("Sets redirectPath", () => {
|
||||
commit(environment, { path: "/path" });
|
||||
it("Sets redirectPath", async () => {
|
||||
const storage = createInMemoryStorage();
|
||||
await commit(environment, { path: "/path" }, {
|
||||
localStorage: createPromisifiedStorage(storage),
|
||||
} as any);
|
||||
expect(source.get(LOCAL_ID)!.redirectPath).toEqual("/path");
|
||||
expect(storage.getItem(REDIRECT_PATH_KEY)).toEqual("/path");
|
||||
});
|
||||
|
||||
it("Removes redirectPath", async () => {
|
||||
const storage = createInMemoryStorage();
|
||||
await commit(environment, { path: null }, {
|
||||
localStorage: createPromisifiedStorage(storage),
|
||||
} as any);
|
||||
expect(source.get(LOCAL_ID)!.redirectPath).toEqual(null);
|
||||
expect(storage.getItem(REDIRECT_PATH_KEY)).toEqual(null);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { REDIRECT_PATH_KEY } from "talk-admin/constants";
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { createMutationContainer, LOCAL_ID } from "talk-framework/lib/relay";
|
||||
|
||||
export interface SetRedirectPathInput {
|
||||
@@ -12,8 +14,15 @@ export type SetRedirectPathMutation = (
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: SetRedirectPathInput
|
||||
input: SetRedirectPathInput,
|
||||
{ localStorage }: TalkContext
|
||||
) {
|
||||
if (!input.path) {
|
||||
await localStorage.removeItem(REDIRECT_PATH_KEY);
|
||||
} else {
|
||||
await localStorage.setItem(REDIRECT_PATH_KEY, input.path);
|
||||
}
|
||||
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(input.path, "redirectPath");
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { graphql } from "react-relay";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
createMutationContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
import { Omit } from "talk-framework/types";
|
||||
|
||||
import { SetUsernameMutation as MutationTypes } from "talk-admin/__generated__/SetUsernameMutation.graphql";
|
||||
|
||||
export type SetUsernameInput = Omit<
|
||||
MutationTypes["variables"]["input"],
|
||||
"clientMutationId"
|
||||
>;
|
||||
|
||||
const mutation = graphql`
|
||||
mutation SetUsernameMutation($input: SetUsernameInput!) {
|
||||
setUsername(input: $input) {
|
||||
user {
|
||||
username
|
||||
}
|
||||
clientMutationId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let clientMutationId = 0;
|
||||
|
||||
function commit(environment: Environment, input: SetUsernameInput) {
|
||||
return commitMutationPromiseNormalized<MutationTypes>(environment, {
|
||||
mutation,
|
||||
variables: {
|
||||
input: {
|
||||
...input,
|
||||
clientMutationId: (clientMutationId++).toString(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const withSetUsernameMutation = createMutationContainer(
|
||||
"setUsername",
|
||||
commit
|
||||
);
|
||||
|
||||
export type SetUsernameMutation = (
|
||||
input: SetUsernameInput
|
||||
) => Promise<MutationTypes["response"]["setUsername"]>;
|
||||
@@ -2,7 +2,6 @@ import { Environment } from "relay-runtime";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
import { commit as setAuthToken } from "talk-framework/mutations/SetAuthTokenMutation";
|
||||
import { signIn, SignInInput } from "talk-framework/rest";
|
||||
|
||||
export type SignInMutation = (input: SignInInput) => Promise<void>;
|
||||
@@ -13,7 +12,13 @@ export async function commit(
|
||||
context: TalkContext
|
||||
) {
|
||||
const result = await signIn(context.rest, input);
|
||||
setAuthToken(environment, { authToken: result.token }, context);
|
||||
|
||||
// Put the token on the hash and clean the session.
|
||||
// It'll be picked up by initLocalState.
|
||||
location.hash = `accessToken=${result.token}`;
|
||||
await context.clearSession();
|
||||
// TODO: (cvle) A better way would be if `context.clearSession` would return the new session and
|
||||
// we set the accessToken directly in there.
|
||||
}
|
||||
|
||||
export const withSignInMutation = createMutationContainer("signIn", commit);
|
||||
|
||||
@@ -20,3 +20,24 @@ export {
|
||||
withRegenerateSSOKeyMutation,
|
||||
RegenerateSSOKeyMutation,
|
||||
} from "./RegenerateSSOKeyMutation";
|
||||
export {
|
||||
withSetAuthViewMutation,
|
||||
SetAuthViewMutation,
|
||||
} from "./SetAuthViewMutation";
|
||||
export {
|
||||
withClearAuthErrorMutation,
|
||||
ClearAuthErrorMutation,
|
||||
} from "./ClearAuthErrorMutation";
|
||||
export {
|
||||
withCompleteAccountMutation,
|
||||
CompleteAccountMutation,
|
||||
} from "./CompleteAccountMutation";
|
||||
export { withSetEmailMutation, SetEmailMutation } from "./SetEmailMutation";
|
||||
export {
|
||||
withSetUsernameMutation,
|
||||
SetUsernameMutation,
|
||||
} from "./SetUsernameMutation";
|
||||
export {
|
||||
withSetPasswordMutation,
|
||||
SetPasswordMutation,
|
||||
} from "./SetPasswordMutation";
|
||||
|
||||
Reference in New Issue
Block a user