diff --git a/.changeset/config.json b/.changeset/config.json index 3f8b12f1e..f9fc0a3a8 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -24,7 +24,8 @@ "@examples/magic-link-server-typescript", "@examples/react-graphql-typescript", "@examples/react-rest-typescript", - "@examples/rest-express-typescript" + "@examples/rest-express-typescript", + "@examples/rest-express-typescript-without-modules" ], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "onlyUpdatePeerDependentsWhenOutOfRange": true diff --git a/examples/rest-express-typescript-without-modules/README.md b/examples/rest-express-typescript-without-modules/README.md new file mode 100644 index 000000000..b6dd7d15d --- /dev/null +++ b/examples/rest-express-typescript-without-modules/README.md @@ -0,0 +1,24 @@ +# rest-express-typescript + +This example demonstrate how to use [accounts-js](https://github.com/accounts-js/accounts). + +You must have a mongodb server running before starting the server. + +## Setup example + +In order to be able to run this example on your machine you first need to do the following steps: + +- Clone the repository `git clone git@github.com:accounts-js/accounts.git` +- Install project dependencies: `yarn install` +- Compile the packages `yarn run compile` +- Go to the example folder `cd examples/rest-express-typescript` + +## Getting Started + +Start the app. + +``` +yarn run start +``` + +Open a browser and navigate to [http://localhost:4000](http://localhost:4000). diff --git a/examples/rest-express-typescript-without-modules/now.json b/examples/rest-express-typescript-without-modules/now.json new file mode 100644 index 000000000..f89f4eb95 --- /dev/null +++ b/examples/rest-express-typescript-without-modules/now.json @@ -0,0 +1,19 @@ +{ + "version": 2, + "name": "rest-express-typescript", + "builds": [ + { + "src": "lib/index.js", + "use": "@now/node-server" + } + ], + "routes": [ + { + "src": ".*", + "dest": "/lib/index.js" + } + ], + "env": { + "MONGO_URL": "@accounts-js-rest-express" + } +} diff --git a/examples/rest-express-typescript-without-modules/package.json b/examples/rest-express-typescript-without-modules/package.json new file mode 100644 index 000000000..a17a7df4c --- /dev/null +++ b/examples/rest-express-typescript-without-modules/package.json @@ -0,0 +1,23 @@ +{ + "name": "@examples/rest-express-typescript-without-modules", + "private": true, + "version": "0.32.0", + "main": "lib/index.js", + "license": "MIT", + "scripts": { + "start": "NODE_ENV=development yarn run -T nodemon -w src -x ts-node src/index.ts", + "build": "yarn run -T tsc", + "test": "yarn run build" + }, + "dependencies": { + "@accounts/mongo": "^0.34.1", + "@accounts/password": "^0.32.2", + "@accounts/rest-express": "^0.33.1", + "@accounts/server": "^0.33.1", + "body-parser": "1.20.2", + "cors": "2.8.5", + "express": "4.18.2", + "mongoose": "8.0.1", + "tslib": "2.6.2" + } +} diff --git a/examples/rest-express-typescript-without-modules/src/index.ts b/examples/rest-express-typescript-without-modules/src/index.ts new file mode 100644 index 000000000..3408e338b --- /dev/null +++ b/examples/rest-express-typescript-without-modules/src/index.ts @@ -0,0 +1,92 @@ +import 'reflect-metadata'; +import express from 'express'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import mongoose from 'mongoose'; +import { AccountsServer, ServerHooks } from '@accounts/server'; +import { AccountsPassword } from '@accounts/password'; +import accountsExpress, { userLoader } from '@accounts/rest-express'; +import { Mongo } from '@accounts/mongo'; + +mongoose.connect(process.env.MONGO_URL || 'mongodb://localhost:27017/accounts-js-rest-example'); +const db = mongoose.connection; + +const app = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(cors()); + +interface UserDoc extends mongoose.Document { + firstName: string; + lastName: string; +} + +const User = mongoose.model( + 'User', + new mongoose.Schema({ firstName: String, lastName: String }) +); + +const accountsPassword = new AccountsPassword({ + // This option is called when a new user create an account + // Inside we can apply our logic to validate the user fields + validateNewUser: (user) => { + // For example we can allow only some kind of emails + if (user.email.endsWith('.xyz')) { + throw new Error('Invalid email'); + } + return user; + }, +}); + +const accountsServer = new AccountsServer( + { + tokenSecret: 'secret', + }, + { + password: accountsPassword, + }, + new Mongo(db) +); + +accountsServer.on(ServerHooks.ValidateLogin, ({ user }) => { + // This hook is called every time a user try to login. + // You can use it to only allow users with verified email to login. + // If you throw an error here it will be returned to the client. + console.log('Logged in', user); +}); + +/** + * Load and expose the accounts-js middleware + */ +app.use(accountsExpress(accountsServer)); + +/** + * Return the current logged in user + */ +app.get('/user', userLoader(accountsServer), (req, res) => { + res.json({ user: (req as any).user }); +}); + +/** + * Expose a public route to edit user informations + * - route is protected + * - update the current logged in user in the db + */ +app.put('/user', userLoader(accountsServer), async (req, res) => { + const userId = (req as any).userId; + if (!userId) { + res.status(401); + res.json({ message: 'Unauthorized' }); + return; + } + const user = await User.findById(userId).exec(); + user.firstName = req.body.firstName; + user.lastName = req.body.lastName; + await user.save(); + res.json(true); +}); + +app.listen(process.env.PORT || 4000, () => { + console.log('Server listening on port 4000'); +}); diff --git a/examples/rest-express-typescript-without-modules/tsconfig.json b/examples/rest-express-typescript-without-modules/tsconfig.json new file mode 100644 index 000000000..9eaa4a7ef --- /dev/null +++ b/examples/rest-express-typescript-without-modules/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "outDir": "./lib", + "target": "es5", + "lib": ["es2015", "esnext.asynciterable"], + "sourceMap": true, + "importHelpers": true, + "skipLibCheck": true, + "isolatedModules": true, + "esModuleInterop": true + }, + "include": ["./src/**/*"] +} diff --git a/examples/rest-express-typescript/package.json b/examples/rest-express-typescript/package.json index e65bfd9da..3ced5919d 100644 --- a/examples/rest-express-typescript/package.json +++ b/examples/rest-express-typescript/package.json @@ -10,6 +10,9 @@ "test": "yarn run build" }, "dependencies": { + "@accounts/module-core": "^0.34.0", + "@accounts/module-mongo": "^0.34.0", + "@accounts/module-password": "^0.34.0", "@accounts/mongo": "^0.34.1", "@accounts/password": "^0.32.2", "@accounts/rest-express": "^0.33.1", diff --git a/examples/rest-express-typescript/src/index.ts b/examples/rest-express-typescript/src/index.ts index 3408e338b..2d62be707 100644 --- a/examples/rest-express-typescript/src/index.ts +++ b/examples/rest-express-typescript/src/index.ts @@ -3,19 +3,54 @@ import express from 'express'; import bodyParser from 'body-parser'; import cors from 'cors'; import mongoose from 'mongoose'; -import { AccountsServer, ServerHooks } from '@accounts/server'; +import { AccountsServer, AuthenticationServicesToken, ServerHooks } from '@accounts/server'; import { AccountsPassword } from '@accounts/password'; import accountsExpress, { userLoader } from '@accounts/rest-express'; -import { Mongo } from '@accounts/mongo'; +import { createApplication } from 'graphql-modules'; +import { createAccountsCoreModule } from '@accounts/module-core'; +import { createAccountsPasswordModule } from '@accounts/module-password'; +import { createAccountsMongoModule } from '@accounts/module-mongo'; mongoose.connect(process.env.MONGO_URL || 'mongodb://localhost:27017/accounts-js-rest-example'); -const db = mongoose.connection; +const dbConn = mongoose.connection; -const app = express(); +const app = createApplication({ + modules: [ + createAccountsCoreModule({ tokenSecret: 'secret' }), + createAccountsPasswordModule({ + // This option is called when a new user create an account + // Inside we can apply our logic to validate the user fields + validateNewUser: (user) => { + if (!user.firstName) { + throw new Error('First name required'); + } + if (!user.lastName) { + throw new Error('Last name required'); + } -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(cors()); + // For example we can allow only some kind of emails + if (user.email.endsWith('.xyz')) { + throw new Error('Invalid email'); + } + return user; + }, + }), + createAccountsMongoModule({ dbConn }), + ], + providers: [ + { + provide: AuthenticationServicesToken, + useValue: { password: AccountsPassword }, + global: true, + }, + ], +}); + +const expressApp = express(); + +expressApp.use(bodyParser.json()); +expressApp.use(bodyParser.urlencoded({ extended: true })); +expressApp.use(cors()); interface UserDoc extends mongoose.Document { firstName: string; @@ -27,27 +62,11 @@ const User = mongoose.model( new mongoose.Schema({ firstName: String, lastName: String }) ); -const accountsPassword = new AccountsPassword({ - // This option is called when a new user create an account - // Inside we can apply our logic to validate the user fields - validateNewUser: (user) => { - // For example we can allow only some kind of emails - if (user.email.endsWith('.xyz')) { - throw new Error('Invalid email'); - } - return user; - }, +const controller = app.createOperationController({ + context: {}, }); - -const accountsServer = new AccountsServer( - { - tokenSecret: 'secret', - }, - { - password: accountsPassword, - }, - new Mongo(db) -); +const accountsServer = controller.injector.get(AccountsServer); +expressApp.use(accountsExpress(accountsServer)); accountsServer.on(ServerHooks.ValidateLogin, ({ user }) => { // This hook is called every time a user try to login. @@ -59,12 +78,12 @@ accountsServer.on(ServerHooks.ValidateLogin, ({ user }) => { /** * Load and expose the accounts-js middleware */ -app.use(accountsExpress(accountsServer)); +expressApp.use(accountsExpress(accountsServer)); /** * Return the current logged in user */ -app.get('/user', userLoader(accountsServer), (req, res) => { +expressApp.get('/user', userLoader(accountsServer), (req, res) => { res.json({ user: (req as any).user }); }); @@ -73,7 +92,7 @@ app.get('/user', userLoader(accountsServer), (req, res) => { * - route is protected * - update the current logged in user in the db */ -app.put('/user', userLoader(accountsServer), async (req, res) => { +expressApp.put('/user', userLoader(accountsServer), async (req, res) => { const userId = (req as any).userId; if (!userId) { res.status(401); @@ -87,6 +106,6 @@ app.put('/user', userLoader(accountsServer), async (req, res) => { res.json(true); }); -app.listen(process.env.PORT || 4000, () => { +expressApp.listen(process.env.PORT || 4000, () => { console.log('Server listening on port 4000'); }); diff --git a/yarn.lock b/yarn.lock index e43b899e3..581be4c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4011,10 +4011,29 @@ __metadata: languageName: unknown linkType: soft +"@examples/rest-express-typescript-without-modules@workspace:examples/rest-express-typescript-without-modules": + version: 0.0.0-use.local + resolution: "@examples/rest-express-typescript-without-modules@workspace:examples/rest-express-typescript-without-modules" + dependencies: + "@accounts/mongo": "npm:^0.34.1" + "@accounts/password": "npm:^0.32.2" + "@accounts/rest-express": "npm:^0.33.1" + "@accounts/server": "npm:^0.33.1" + body-parser: "npm:1.20.2" + cors: "npm:2.8.5" + express: "npm:4.18.2" + mongoose: "npm:8.0.1" + tslib: "npm:2.6.2" + languageName: unknown + linkType: soft + "@examples/rest-express-typescript@workspace:examples/rest-express-typescript": version: 0.0.0-use.local resolution: "@examples/rest-express-typescript@workspace:examples/rest-express-typescript" dependencies: + "@accounts/module-core": "npm:^0.34.0" + "@accounts/module-mongo": "npm:^0.34.0" + "@accounts/module-password": "npm:^0.34.0" "@accounts/mongo": "npm:^0.34.1" "@accounts/password": "npm:^0.32.2" "@accounts/rest-express": "npm:^0.33.1"