mirror of
https://github.com/wassname/talk.git
synced 2026-07-31 12:50:48 +08:00
[next] Admin Configure (#2076)
* feat: Add RadioButton and CheckBox * feat: configure facebook and google auth * feat: configure sso, localAuth and displayName + some tests * test: add integration tests for configure auth * test: more integration tests * feat: add oidc support * test: add oidc integration test * feat: generate sso key initially * fix: import fetchQuery from correct package * fix: admin url * fix: set timezone to utc when testing * refactor: improve route config * fix: remove obsolete line * fix: clientMutationId increment * fix: oidc only create when enabled * fix: copy * test: update snapshots * feat: fixed graphql logging extension * Update src/locales/en-US/admin.ftl Co-Authored-By: cvle <vinh@wikiwi.io> * Apply suggestions from code review Co-Authored-By: cvle <vinh@wikiwi.io> * test: update snapshots * fix: change Local Auth to Email Authentication * fix: copy updates
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
import { formatApolloErrors } from "apollo-server-errors";
|
||||
import { GraphQLError } from "graphql";
|
||||
import { GraphQLExtension, GraphQLResponse } from "graphql-extensions";
|
||||
import {
|
||||
DocumentNode,
|
||||
ExecutionArgs,
|
||||
GraphQLError,
|
||||
OperationDefinitionNode,
|
||||
} from "graphql";
|
||||
import {
|
||||
EndHandler,
|
||||
GraphQLExtension,
|
||||
GraphQLResponse,
|
||||
} from "graphql-extensions";
|
||||
import now from "performance-now";
|
||||
|
||||
import CommonContext from "talk-server/graph/common/context";
|
||||
|
||||
@@ -15,11 +25,49 @@ export class LoggerExtension implements GraphQLExtension<CommonContext> {
|
||||
return err;
|
||||
};
|
||||
|
||||
public requestDidStart(o: {
|
||||
operationName?: string;
|
||||
context: CommonContext;
|
||||
}) {
|
||||
o.context.logger.debug({ operationName: o.operationName }, "graphql query");
|
||||
private getOperationMetadata(doc: DocumentNode) {
|
||||
if (doc.kind === "Document") {
|
||||
const operationDefinition = doc.definitions.find(
|
||||
({ kind }) => kind === "OperationDefinition"
|
||||
) as OperationDefinitionNode | undefined;
|
||||
if (operationDefinition) {
|
||||
let operationName: string | undefined;
|
||||
if (operationDefinition.name) {
|
||||
operationName = operationDefinition.name.value;
|
||||
}
|
||||
|
||||
return {
|
||||
operationName,
|
||||
operation: operationDefinition.operation,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public executionDidStart(o: {
|
||||
executionArgs: ExecutionArgs;
|
||||
}): EndHandler | void {
|
||||
// Only try to log things if the context is provided.
|
||||
if (o.executionArgs.contextValue) {
|
||||
// Grab the start time so we can calculate the time it takes to execute
|
||||
// the graph query.
|
||||
const startTime = now();
|
||||
return () => {
|
||||
// Compute the end time.
|
||||
const responseTime = Math.round(now() - startTime);
|
||||
|
||||
// Log out the details of the request.
|
||||
o.executionArgs.contextValue.logger.debug(
|
||||
{
|
||||
responseTime,
|
||||
...this.getOperationMetadata(o.executionArgs.document),
|
||||
},
|
||||
"graphql query"
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public willSendResponse(o: {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { constructTenantURL, reconstructURL } from "talk-server/app/url";
|
||||
import {
|
||||
GQLFacebookAuthIntegration,
|
||||
GQLFacebookAuthIntegrationTypeResolver,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
const FacebookAuthIntegration: GQLFacebookAuthIntegrationTypeResolver<
|
||||
GQLFacebookAuthIntegration
|
||||
> = {
|
||||
callbackURL: (integration, args, ctx) => {
|
||||
const path = `/api/tenant/auth/facebook/callback`;
|
||||
|
||||
// If the request is available, then prefer it over building from the tenant
|
||||
// as the tenant does not include the port number. This should only really
|
||||
// be a problem if the graph API is called internally.
|
||||
if (ctx.req) {
|
||||
return reconstructURL(ctx.req, path);
|
||||
}
|
||||
|
||||
// Note that when constructing the callback url with the tenant, the port
|
||||
// information is lost.
|
||||
return constructTenantURL(ctx.config, ctx.tenant, path);
|
||||
},
|
||||
};
|
||||
|
||||
export default FacebookAuthIntegration;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { constructTenantURL, reconstructURL } from "talk-server/app/url";
|
||||
import {
|
||||
GQLGoogleAuthIntegration,
|
||||
GQLGoogleAuthIntegrationTypeResolver,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
|
||||
const GoogleAuthIntegration: GQLGoogleAuthIntegrationTypeResolver<
|
||||
GQLGoogleAuthIntegration
|
||||
> = {
|
||||
callbackURL: (integration, args, ctx) => {
|
||||
const path = `/api/tenant/auth/google/callback`;
|
||||
|
||||
// If the request is available, then prefer it over building from the tenant
|
||||
// as the tenant does not include the port number. This should only really
|
||||
// be a problem if the graph API is called internally.
|
||||
if (ctx.req) {
|
||||
return reconstructURL(ctx.req, path);
|
||||
}
|
||||
|
||||
// Note that when constructing the callback url with the tenant, the port
|
||||
// information is lost.
|
||||
return constructTenantURL(ctx.config, ctx.tenant, path);
|
||||
},
|
||||
};
|
||||
|
||||
export default GoogleAuthIntegration;
|
||||
@@ -6,6 +6,8 @@ import { GQLResolver } from "talk-server/graph/tenant/schema/__generated__/types
|
||||
import AuthIntegrations from "./auth_integrations";
|
||||
import Comment from "./comment";
|
||||
import CommentCounts from "./comment_counts";
|
||||
import FacebookAuthIntegration from "./facebook_auth_integration";
|
||||
import GoogleAuthIntegration from "./google_auth_integration";
|
||||
import Mutation from "./mutation";
|
||||
import OIDCAuthIntegration from "./oidc_auth_integration";
|
||||
import Profile from "./profile";
|
||||
@@ -20,6 +22,8 @@ const Resolvers: GQLResolver = {
|
||||
Cursor,
|
||||
Mutation,
|
||||
OIDCAuthIntegration,
|
||||
FacebookAuthIntegration,
|
||||
GoogleAuthIntegration,
|
||||
Profile,
|
||||
Query,
|
||||
Time,
|
||||
|
||||
@@ -275,7 +275,7 @@ type LocalAuthIntegration {
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: AuthenticationTargetFilter
|
||||
targetFilter: AuthenticationTargetFilter!
|
||||
}
|
||||
|
||||
##########################
|
||||
@@ -301,7 +301,7 @@ type SSOAuthIntegration {
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: AuthenticationTargetFilter
|
||||
targetFilter: AuthenticationTargetFilter!
|
||||
|
||||
"""
|
||||
key is the secret that is used to sign tokens.
|
||||
@@ -385,7 +385,7 @@ type OIDCAuthIntegration {
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: AuthenticationTargetFilter
|
||||
targetFilter: AuthenticationTargetFilter!
|
||||
|
||||
"""
|
||||
name is the label assigned to reference the provider of the OIDC integration,
|
||||
@@ -462,10 +462,17 @@ type GoogleAuthIntegration {
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: AuthenticationTargetFilter
|
||||
targetFilter: AuthenticationTargetFilter!
|
||||
|
||||
clientID: String @auth(roles: [ADMIN])
|
||||
clientSecret: String @auth(roles: [ADMIN])
|
||||
|
||||
"""
|
||||
callbackURL is the URL that the user should be redirected to in order to start
|
||||
the authentication flow. This field is not stored, and is instead computed from
|
||||
the Tenant.
|
||||
"""
|
||||
callbackURL: String!
|
||||
}
|
||||
|
||||
##########################
|
||||
@@ -486,10 +493,17 @@ type FacebookAuthIntegration {
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: AuthenticationTargetFilter
|
||||
targetFilter: AuthenticationTargetFilter!
|
||||
|
||||
clientID: String @auth(roles: [ADMIN])
|
||||
clientSecret: String @auth(roles: [ADMIN])
|
||||
|
||||
"""
|
||||
callbackURL is the URL that the user should be redirected to in order to start
|
||||
the authentication flow. This field is not stored, and is instead computed from
|
||||
the Tenant.
|
||||
"""
|
||||
callbackURL: String!
|
||||
}
|
||||
|
||||
##########################
|
||||
@@ -530,7 +544,7 @@ type Auth {
|
||||
displayName contains configuration related to the use of Display Names across
|
||||
AuthIntegrations.
|
||||
"""
|
||||
displayName: AuthDisplayNameConfiguration @auth(roles: [ADMIN])
|
||||
displayName: AuthDisplayNameConfiguration! @auth(roles: [ADMIN])
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@@ -1598,11 +1612,25 @@ input SettingsAuthIntegrationsInput {
|
||||
facebook: SettingsFacebookAuthIntegrationInput
|
||||
}
|
||||
|
||||
input SettingsAuthDisplayNameInput {
|
||||
"""
|
||||
enabled when true will allow the display name to be used by other
|
||||
AuthIntegrations.
|
||||
"""
|
||||
enabled: Boolean!
|
||||
}
|
||||
|
||||
"""
|
||||
Auth contains all the settings related to authentication and
|
||||
authorization.
|
||||
"""
|
||||
input SettingsAuthInput {
|
||||
"""
|
||||
"
|
||||
displayName allows configuration related to Display Names.
|
||||
"""
|
||||
displayName: SettingsAuthDisplayNameInput
|
||||
|
||||
"""
|
||||
integrations are the set of configurations for the variations of
|
||||
authentication solutions.
|
||||
@@ -2090,6 +2118,24 @@ type RegenerateSSOKeyPayload {
|
||||
##################
|
||||
|
||||
input CreateOIDCAuthIntegrationConfigurationInput {
|
||||
"""
|
||||
enabled, when true, allows the integration to be enabled.
|
||||
"""
|
||||
enabled: Boolean
|
||||
|
||||
"""
|
||||
allowRegistration when true will allow users that have not signed up
|
||||
before with this authentication integration to sign up.
|
||||
"""
|
||||
allowRegistration: Boolean
|
||||
|
||||
"""
|
||||
targetFilter will restrict where the authentication integration should be
|
||||
displayed. If the value of targetFilter is null, then the authentication
|
||||
integration should be displayed in all targets.
|
||||
"""
|
||||
targetFilter: SettingsAuthenticationTargetFilterInput
|
||||
|
||||
"""
|
||||
name is the label assigned to reference the provider of the OIDC integration,
|
||||
and will be used in situations where the name of the provider needs to be
|
||||
|
||||
Reference in New Issue
Block a user