diff --git a/packages/nocodb-nest/package-lock.json b/packages/nocodb-nest/package-lock.json index 0d4c1bdeaa..e4f7bc69cd 100644 --- a/packages/nocodb-nest/package-lock.json +++ b/packages/nocodb-nest/package-lock.json @@ -20,7 +20,6 @@ "@nestjs/serve-static": "^3.0.1", "@sentry/node": "^6.3.5", "@types/chai": "^4.2.12", - "@types/mocha": "^8.0.1", "airtable": "^0.11.3", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -116,9 +115,11 @@ "@nestjs/testing": "^9.0.0", "@nestjsplus/dyn-schematics": "^1.0.12", "@types/express": "^4.17.13", - "@types/jest": "29.5.0", + "@types/jest": "^29.5.0", + "@types/mocha": "^10.0.1", "@types/multer": "^1.4.7", "@types/node": "18.15.11", + "@types/passport-google-oauth20": "^2.0.11", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", @@ -3350,9 +3351,10 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true }, "node_modules/@types/multer": { "version": "1.4.7", @@ -3390,6 +3392,15 @@ "node": ">= 6" } }, + "node_modules/@types/oauth": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz", + "integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3405,6 +3416,17 @@ "@types/express": "*" } }, + "node_modules/@types/passport-google-oauth20": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.11.tgz", + "integrity": "sha512-9XMT1GfwhZL7UQEiCepLef55RNPHkbrCtsU7rsWPTEOsmu5qVIW8nSemtB4p+P24CuOhA+IKkv8LsPThYghGww==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, "node_modules/@types/passport-jwt": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.8.tgz", @@ -3416,6 +3438,17 @@ "@types/passport-strategy": "*" } }, + "node_modules/@types/passport-oauth2": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.12.tgz", + "integrity": "sha512-RZg6cYTyEGinrZn/7REYQds6zrTxoBorX1/fdaz5UHzkG8xdFE7QQxkJagCr2ETzGII58FAFDmnmbTUVMrltNA==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "node_modules/@types/passport-strategy": { "version": "0.2.35", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", @@ -20752,9 +20785,10 @@ "dev": true }, "@types/mocha": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==" + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true }, "@types/multer": { "version": "1.4.7", @@ -20791,6 +20825,15 @@ } } }, + "@types/oauth": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.1.tgz", + "integrity": "sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -20806,6 +20849,17 @@ "@types/express": "*" } }, + "@types/passport-google-oauth20": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.11.tgz", + "integrity": "sha512-9XMT1GfwhZL7UQEiCepLef55RNPHkbrCtsU7rsWPTEOsmu5qVIW8nSemtB4p+P24CuOhA+IKkv8LsPThYghGww==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, "@types/passport-jwt": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.8.tgz", @@ -20817,6 +20871,17 @@ "@types/passport-strategy": "*" } }, + "@types/passport-oauth2": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.4.12.tgz", + "integrity": "sha512-RZg6cYTyEGinrZn/7REYQds6zrTxoBorX1/fdaz5UHzkG8xdFE7QQxkJagCr2ETzGII58FAFDmnmbTUVMrltNA==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "@types/passport-strategy": { "version": "0.2.35", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", diff --git a/packages/nocodb-nest/package.json b/packages/nocodb-nest/package.json index eea43fe1f6..955172eef8 100644 --- a/packages/nocodb-nest/package.json +++ b/packages/nocodb-nest/package.json @@ -50,7 +50,6 @@ "@nestjs/serve-static": "^3.0.1", "@sentry/node": "^6.3.5", "@types/chai": "^4.2.12", - "@types/mocha": "^8.0.1", "airtable": "^0.11.3", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", @@ -146,9 +145,11 @@ "@nestjs/testing": "^9.0.0", "@nestjsplus/dyn-schematics": "^1.0.12", "@types/express": "^4.17.13", - "@types/jest": "29.5.0", + "@types/jest": "^29.5.0", + "@types/mocha": "^10.0.1", "@types/multer": "^1.4.7", "@types/node": "18.15.11", + "@types/passport-google-oauth20": "^2.0.11", "@types/passport-jwt": "^3.0.8", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", diff --git a/packages/nocodb-nest/src/app.module.ts b/packages/nocodb-nest/src/app.module.ts index 6e56b84d7e..9e9f05ddb4 100644 --- a/packages/nocodb-nest/src/app.module.ts +++ b/packages/nocodb-nest/src/app.module.ts @@ -46,7 +46,6 @@ import type { LocalStrategy, AuthTokenStrategy, BaseViewStrategy, - GoogleStrategy, ], }) export class AppModule implements OnApplicationBootstrap { diff --git a/packages/nocodb-nest/src/controllers/users/users.controller.ts b/packages/nocodb-nest/src/controllers/users/users.controller.ts index c162c66079..47d8072105 100644 --- a/packages/nocodb-nest/src/controllers/users/users.controller.ts +++ b/packages/nocodb-nest/src/controllers/users/users.controller.ts @@ -20,6 +20,7 @@ import { ExtractProjectIdMiddleware, } from '../../middlewares/extract-project-id/extract-project-id.middleware'; import Noco from '../../Noco'; +import { GoogleStrategy } from '../../strategies/google.strategy/google.strategy'; import extractRolesObj from '../../utils/extractRolesObj'; import { Audit, User } from '../../models'; import { @@ -31,7 +32,10 @@ import { UsersService } from '../../services/users/users.service'; @Controller() export class UsersController { - constructor(private readonly usersService: UsersService) {} + constructor( + private readonly usersService: UsersService, + private googleStrategy: GoogleStrategy, + ) {} @Post([ '/auth/user/signup', @@ -131,33 +135,21 @@ export class UsersController { @Post(`/auth/google/genTokenByCode`) @HttpCode(200) - async googleSignin(req, res, next) { - // todo - /* passport.authenticate( - 'google', - { - session: false, - callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath, - }, - async (err, user, info): Promise => - await successfulSignIn({ - user, - err, - info, - req, - res, - auditDescription: 'signed in using Google Auth', - }) - )(req, res, next);*/ + @UseGuards(AuthGuard('google')) + async googleSignin(@Request() req) { + return this.usersService.login(req.user); } @Get('/auth/google') - googleAuthenticate() { - /* passport.authenticate('google', { - scope: ['profile', 'email'], - state: req.query.state, - callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath, - })(req, res, next)*/ + @UseGuards( + AuthGuard('google'), + ) + googleAuthenticate(@Request() req) { + // this.googleStrategy.authenticate(req, { + // scope: ['profile', 'email'], + // state: req.query.state, + // callbackURL: req.ncSiteUrl + Noco.getConfig().dashboardPath, + // }); } @Get(['/auth/user/me', '/api/v1/db/auth/user/me', '/api/v1/auth/user/me']) diff --git a/packages/nocodb-nest/src/meta/meta.service.ts b/packages/nocodb-nest/src/meta/meta.service.ts index 0f78335a66..e68cda8274 100644 --- a/packages/nocodb-nest/src/meta/meta.service.ts +++ b/packages/nocodb-nest/src/meta/meta.service.ts @@ -1043,7 +1043,6 @@ export class MetaService { } public async init(): Promise { - NocoCache.init(); await this.connection.migrate.latest({ migrationSource: new XcMigrationSource(), tableName: 'xc_knex_migrations', diff --git a/packages/nocodb-nest/src/modules/users/users.module.ts b/packages/nocodb-nest/src/modules/users/users.module.ts index dffd58f911..8c18517c5c 100644 --- a/packages/nocodb-nest/src/modules/users/users.module.ts +++ b/packages/nocodb-nest/src/modules/users/users.module.ts @@ -1,12 +1,14 @@ import { Module } from '@nestjs/common'; +import { GoogleStrategy, GoogleStrategyProvider } from '../../strategies/google.strategy/google.strategy' import { GlobalModule } from '../global/global.module'; import { UsersService } from '../../services/users/users.service'; import { UsersController } from '../../controllers/users/users.controller'; +import { PassportModule } from '@nestjs/passport'; @Module({ - imports: [GlobalModule], + imports: [GlobalModule, PassportModule], controllers: [UsersController], - providers: [UsersService], + providers: [UsersService, GoogleStrategyProvider], exports: [UsersService], }) export class UsersModule {} diff --git a/packages/nocodb-nest/src/strategies/google.strategy/google.strategy.ts b/packages/nocodb-nest/src/strategies/google.strategy/google.strategy.ts index 91c7a1515f..7f1bf3db5c 100644 --- a/packages/nocodb-nest/src/strategies/google.strategy/google.strategy.ts +++ b/packages/nocodb-nest/src/strategies/google.strategy/google.strategy.ts @@ -1,4 +1,104 @@ -import { Injectable } from '@nestjs/common'; +import { promisify } from 'util'; +import { Injectable, Optional } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { Strategy } from 'passport-google-oauth20'; +import bcrypt from 'bcryptjs'; +import { Plugin, ProjectUser, User } from '../../models'; +import { UsersService } from '../../services/users/users.service'; +import type { VerifyCallback } from 'passport-google-oauth20'; +import type { FactoryProvider } from '@nestjs/common/interfaces/modules/provider.interface'; @Injectable() -export class GoogleStrategy {} +export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { + constructor( + @Optional() clientConfig: any, + private usersService: UsersService, + ) { + super(clientConfig); + } + + async validate( + req: any, + accessToken: string, + refreshToken: string, + profile: any, + done: VerifyCallback, + ): Promise { + // mostly copied from older code + const email = profile.emails[0].value; + try { + const user = await User.getByEmail(email); + if (user) { + // if project id defined extract project level roles + if (req.ncProjectId) { + ProjectUser.get(req.ncProjectId, user.id) + .then(async (projectUser) => { + user.roles = projectUser?.roles || user.roles; + user.roles = + user.roles === 'owner' ? 'owner,creator' : user.roles; + // + (user.roles ? `,${user.roles}` : ''); + + done(null, user); + }) + .catch((e) => done(e)); + } else { + return done(null, user); + } + // if user not found create new user if allowed + // or return error + } else { + const salt = await promisify(bcrypt.genSalt)(10); + const user = await this.usersService.registerNewUserIfAllowed({ + firstname: null, + lastname: null, + email_verification_token: null, + email: profile.emails[0].value, + password: '', + salt, + }); + return done(null, user); + } + } catch (err) { + return done(err); + } + } + + authorizationParams(options: any) { + const params = super.authorizationParams(options) as Record; + + if (options.state) { + params.state = options.state; + } + + return params; + } +} + +export const GoogleStrategyProvider: FactoryProvider = { + provide: GoogleStrategy, + inject: [UsersService], + useFactory: async (usersService: UsersService) => { + // const googlePlugin = await Plugin.getPluginByTitle('Google'); + // + // if (googlePlugin && googlePlugin.input) { + // const settings = JSON.parse(googlePlugin.input); + // process.env.NC_GOOGLE_CLIENT_ID = settings.client_id; + // process.env.NC_GOOGLE_CLIENT_SECRET = settings.client_secret; + // } + if ( + !process.env.NC_GOOGLE_CLIENT_ID || + !process.env.NC_GOOGLE_CLIENT_SECRET + ) + return null; + + const clientConfig = { + clientID: process.env.NC_GOOGLE_CLIENT_ID, + clientSecret: process.env.NC_GOOGLE_CLIENT_SECRET, + // todo: update url + callbackURL: 'http://localhost:3000', + passReqToCallback: true, + }; + + return new GoogleStrategy(clientConfig, usersService); + }, +};