diff --git a/examples/graphql-server-typescript/package.json b/examples/graphql-server-typescript/package.json index 01bff74c6..8f4dd9260 100644 --- a/examples/graphql-server-typescript/package.json +++ b/examples/graphql-server-typescript/package.json @@ -19,11 +19,16 @@ "@envelop/graphql-modules": "6.0.0", "@graphql-tools/merge": "9.0.0", "@graphql-tools/schema": "10.0.0", + "express": "^4.18.2", "graphql": "16.8.1", "graphql-modules": "3.0.0-alpha-20231106133212-0b04b56e", "graphql-tag": "2.12.6", "graphql-yoga": "5.0.0", + "helmet": "^7.1.0", "mongoose": "7.6.4", "tslib": "2.6.2" + }, + "devDependencies": { + "@types/express": "^4.17.21" } } diff --git a/examples/graphql-server-typescript/src/index.ts b/examples/graphql-server-typescript/src/index.ts index 8d88988c3..5f9a027a4 100644 --- a/examples/graphql-server-typescript/src/index.ts +++ b/examples/graphql-server-typescript/src/index.ts @@ -6,15 +6,22 @@ import { createAccountsCoreModule, } from '@accounts/module-core'; import { createAccountsPasswordModule } from '@accounts/module-password'; -import { AccountsPassword } from '@accounts/password'; +import { + AccountsPassword, + infosMiddleware, + resetPassword, + resetPasswordForm, + verifyEmail, +} from '@accounts/password'; import { AccountsServer, AuthenticationServicesToken, ServerHooks } from '@accounts/server'; import gql from 'graphql-tag'; import mongoose from 'mongoose'; import { createApplication } from 'graphql-modules'; import { createAccountsMongoModule } from '@accounts/module-mongo'; -import { createServer } from 'node:http'; import { createYoga } from 'graphql-yoga'; import { useGraphQLModules } from '@envelop/graphql-modules'; +import express from 'express'; +import helmet from 'helmet'; void (async () => { // Create database connection @@ -79,10 +86,14 @@ void (async () => { }, }; + const port = 4000; + const siteUrl = `http://localhost:${port}`; const app = createApplication({ modules: [ - createAccountsCoreModule({ tokenSecret: 'secret' }), + createAccountsCoreModule({ tokenSecret: 'secret', siteUrl }), createAccountsPasswordModule({ + requireEmailVerification: true, + sendVerificationEmailAfterSignup: true, // This option is called when a new user create an account // Inside we can apply our logic to validate the user fields validateNewUser: (user) => { @@ -127,11 +138,39 @@ void (async () => { context: (ctx) => context(ctx, { createOperationController }), }); - // Pass it into a server to hook into request handlers. - const server = createServer(yoga); + const yogaRouter = express.Router(); + // GraphiQL specefic CSP configuration + yogaRouter.use( + helmet({ + contentSecurityPolicy: { + directives: { + 'style-src': ["'self'", 'unpkg.com'], + 'script-src': ["'self'", 'unpkg.com', "'unsafe-inline'"], + 'img-src': ["'self'", 'raw.githubusercontent.com'], + }, + }, + }) + ); + yogaRouter.use(yoga); + + const router = express.Router(); + // By adding the GraphQL Yoga router before the global helmet middleware, + // you can be sure that the global CSP configuration will not be applied to the GraphQL Yoga endpoint + router.use(yoga.graphqlEndpoint, yogaRouter); + // Add the global CSP configuration for the rest of your server. + router.use(helmet()); + router.use(express.urlencoded({ extended: true })); + + router.use(infosMiddleware); + router.get('/verify-email/:token', verifyEmail(app.injector)); + router.get('/reset-password/:token', resetPasswordForm); + router.post('/resetPassword', resetPassword(app.injector)); + + const expressApp = express(); + expressApp.use(router); // Start the server and you're done! - server.listen(4000, () => { - console.info('Server is running on http://localhost:4000/graphql'); + expressApp.listen(port, () => { + console.info(`Server is running on ${siteUrl}/graphql`); }); })(); diff --git a/packages/password/package.json b/packages/password/package.json index 898ea22db..f21250a70 100644 --- a/packages/password/package.json +++ b/packages/password/package.json @@ -24,13 +24,16 @@ "dependencies": { "@accounts/two-factor": "^0.32.4", "bcryptjs": "2.4.3", - "tslib": "2.6.2" + "tslib": "2.6.2", + "validator": "^13.11.0" }, "devDependencies": { "@accounts/server": "^0.33.1", "@accounts/types": "^0.33.1", "@types/bcryptjs": "2.4.6", + "@types/express": "^4.17.21", "@types/lodash.set": "4.3.9", + "@types/validator": "^13", "graphql": "16.8.1", "graphql-modules": "3.0.0-alpha-20231106133212-0b04b56e", "lodash.set": "4.3.2", diff --git a/packages/password/src/endpoints/Request.d.ts b/packages/password/src/endpoints/Request.d.ts new file mode 100644 index 000000000..843a19180 --- /dev/null +++ b/packages/password/src/endpoints/Request.d.ts @@ -0,0 +1,10 @@ +declare namespace Express { + export interface Request { + userAgent: string; + ip: string; + infos: { + userAgent: string; + ip: string; + }; + } +} diff --git a/packages/password/src/endpoints/express.ts b/packages/password/src/endpoints/express.ts new file mode 100644 index 000000000..3ae27f625 --- /dev/null +++ b/packages/password/src/endpoints/express.ts @@ -0,0 +1,109 @@ +import { type Injector } from 'graphql-modules'; +import type { Request, Response, NextFunction } from 'express'; +import validator from 'validator'; +import AccountsPassword from '../accounts-password'; + +function getHtml(title: string, body: string) { + return ` + + +
+