google auth E2E

This commit is contained in:
Martin McKeaveney 2021-04-21 12:12:22 +01:00
parent 1b54845c5e
commit b61a29f2ff
8 changed files with 155 additions and 35 deletions

View File

@ -70,12 +70,11 @@ exports.getUserParams = (email = "", otherProps = {}) => {
* Generates a new configuration ID. * Generates a new configuration ID.
* @returns {string} The new configuration ID which the config doc can be stored under. * @returns {string} The new configuration ID which the config doc can be stored under.
*/ */
exports.generateConfigID = (type = "", group = "") => { exports.generateConfigID = (type = "", group = "", user = "") => {
group += SEPARATOR // group += SEPARATOR
const scope = [type, group, user].join(SEPARATOR)
return `${ return `${DocumentTypes.CONFIG}${SEPARATOR}${scope}${newid()}`
DocumentTypes.CONFIG
}${SEPARATOR}${type}${SEPARATOR}${group}${newid()}`
} }
/** /**

View File

@ -1,10 +1,10 @@
const passport = require("koa-passport") const passport = require("koa-passport")
const LocalStrategy = require("passport-local").Strategy const LocalStrategy = require("passport-local").Strategy
const JwtStrategy = require("passport-jwt").Strategy const JwtStrategy = require("passport-jwt").Strategy
// const GoogleStrategy = require("passport-google-oauth").Strategy const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
const database = require("./db") const database = require("./db")
const { StaticDatabases } = require("./db/utils") const { StaticDatabases, DocumentTypes } = require("./db/utils")
const { jwt, local, authenticated } = require("./middleware") const { jwt, local, google, authenticated } = require("./middleware")
const { Cookies, UserStatus } = require("./constants") const { Cookies, UserStatus } = require("./constants")
const { hash, compare } = require("./hashing") const { hash, compare } = require("./hashing")
const { const {
@ -27,7 +27,7 @@ const {
// Strategies // Strategies
passport.use(new LocalStrategy(local.options, local.authenticate)) passport.use(new LocalStrategy(local.options, local.authenticate))
passport.use(new JwtStrategy(jwt.options, jwt.authenticate)) passport.use(new JwtStrategy(jwt.options, jwt.authenticate))
// passport.use(new GoogleStrategy(google.options, google.authenticate)) passport.use(new GoogleStrategy(google.options, google.authenticate))
passport.serializeUser((user, done) => done(null, user)) passport.serializeUser((user, done) => done(null, user))
@ -50,6 +50,7 @@ module.exports = {
passport, passport,
Cookies, Cookies,
UserStatus, UserStatus,
DocumentTypes,
StaticDatabases, StaticDatabases,
generateUserID, generateUserID,
getUserParams, getUserParams,

View File

@ -1,18 +1,54 @@
const env = require("../../environment") const env = require("../../environment")
const jwt = require("jsonwebtoken")
const database = require("../../db")
const { StaticDatabases, generateUserID } = require("../../db/utils")
exports.options = { exports.options = {
clientId: env.GOOGLE_CLIENT_ID, clientID: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET, clientSecret: env.GOOGLE_CLIENT_SECRET,
callbackURL: env.GOOGLE_AUTH_CALLBACK_URL, callbackURL: env.GOOGLE_AUTH_CALLBACK_URL,
} }
exports.authenticate = async function(token, tokenSecret, profile, done) { exports.authenticate = async function(token, tokenSecret, profile, done) {
console.log({ if (!profile._json.email) return done(null, false, "Email Required.")
token,
tokenSecret, // Check the user exists in the instance DB by email
profile, const db = new database.CouchDB(StaticDatabases.GLOBAL.name)
done,
let dbUser
const userId = generateUserID(profile._json.email)
try {
// use the google profile id
dbUser = await db.get(userId)
} catch (err) {
console.error("Google user not found. Creating..")
// create the user
const user = {
_id: userId,
provider: profile.provider,
roles: {},
builder: {
global: true,
},
...profile._json,
}
const response = await db.post(user)
dbUser = user
dbUser._rev = response.rev
}
// authenticate
const payload = {
userId: dbUser._id,
builder: dbUser.builder,
email: dbUser.email,
}
dbUser.token = jwt.sign(payload, env.JWT_SECRET, {
expiresIn: "1 day",
}) })
// retrieve user ...
// fetchUser().then(user => done(null, user)) return done(null, dbUser)
} }

View File

@ -46,6 +46,7 @@
<Input outline type="password" on:change bind:value={password} /> <Input outline type="password" on:change bind:value={password} />
<Spacer large /> <Spacer large />
<Button primary on:click={login}>Login</Button> <Button primary on:click={login}>Login</Button>
<a target="_blank" href="/api/admin/auth/google">Sign In with Google</a>
<Button secondary on:click={createTestUser}>Create Test User</Button> <Button secondary on:click={createTestUser}>Create Test User</Button>
</form> </form>

View File

@ -1,17 +1,47 @@
const CouchDB = require("../../../db") const CouchDB = require("../../../db")
const { StaticDatabases } = require("@budibase/auth") const { StaticDatabases, DocumentTypes } = require("@budibase/auth")
const { generateConfigID } = require("@budibase/auth") const { generateConfigID, getConfigParams } = require("@budibase/auth")
const { getConfigParams } = require("@budibase/auth/src/db/utils") const { SEPARATOR } = require("@budibase/auth/src/db/utils")
const { Configs } = require("../../../constants")
const GLOBAL_DB = StaticDatabases.GLOBAL.name const GLOBAL_DB = StaticDatabases.GLOBAL.name
exports.configStatus = async function(ctx) {
const db = new CouchDB(GLOBAL_DB)
let configured = {}
// check for super admin user
try {
configured.user = true
} catch (err) {
configured.user = false
}
// check for SMTP config
try {
const response = await db.allDocs(
getConfigParams(`${DocumentTypes.CONFIG}${SEPARATOR}${Configs.SMTP}`)
)
console.log(response)
configured.smtp = true
} catch (err) {
configured.smtp = false
}
ctx.body = configured
}
exports.save = async function(ctx) { exports.save = async function(ctx) {
const db = new CouchDB(GLOBAL_DB) const db = new CouchDB(GLOBAL_DB)
const configDoc = ctx.request.body const configDoc = ctx.request.body
// Config does not exist yet // Config does not exist yet
if (!configDoc._id) { if (!configDoc._id) {
configDoc._id = generateConfigID(configDoc.type, configDoc.group) configDoc._id = generateConfigID(
configDoc.type,
configDoc.group,
configDoc.user
)
} }
try { try {

View File

@ -1,4 +1,38 @@
const { passport, Cookies, clearCookie } = require("@budibase/auth") const {
passport,
Cookies,
StaticDatabases,
clearCookie,
} = require("@budibase/auth")
const CouchDB = require("../../db")
const GLOBAL_DB = StaticDatabases.GLOBAL.name
async function setToken(ctx) {
return async function(err, user) {
if (err) {
return ctx.throw(403, "Unauthorized")
}
const expires = new Date()
expires.setDate(expires.getDate() + 1)
if (!user) {
return ctx.throw(403, "Unauthorized")
}
ctx.cookies.set(Cookies.Auth, user.token, {
expires,
path: "/",
httpOnly: false,
overwrite: true,
})
delete user.token
ctx.body = { user }
}
}
exports.authenticate = async (ctx, next) => { exports.authenticate = async (ctx, next) => {
return passport.authenticate("local", async (err, user) => { return passport.authenticate("local", async (err, user) => {
@ -31,10 +65,30 @@ exports.logout = async ctx => {
ctx.body = { message: "User logged out" } ctx.body = { message: "User logged out" }
} }
exports.googleAuth = async () => { exports.googleAuth = async (ctx, next) => {
// return passport.authenticate("google") return passport.authenticate(
} "google",
{ successRedirect: "/", failureRedirect: "/" },
async (err, user) => {
if (err) {
return ctx.throw(403, "Unauthorized")
}
exports.googleAuth = async () => { const expires = new Date()
// return passport.authenticate("google") expires.setDate(expires.getDate() + 1)
if (!user) {
return ctx.throw(403, "Unauthorized")
}
ctx.cookies.set(Cookies.Auth, user.token, {
expires,
path: "/",
httpOnly: false,
overwrite: true,
})
ctx.redirect("/")
}
)(ctx, next)
} }

View File

@ -10,7 +10,7 @@ const router = Router()
function buildConfigSaveValidation() { function buildConfigSaveValidation() {
// prettier-ignore // prettier-ignore
return joiValidator.body(Joi.object({ return joiValidator.body(Joi.object({
type: Joi.string().valid(...Object.values(Configs)).required() type: Joi.string().valid(...Object.values(Configs)).required(),
}).required().unknown(true)) }).required().unknown(true))
} }
@ -21,6 +21,7 @@ router
authenticated, authenticated,
controller.save controller.save
) )
.post("/api/admin/config/status", controller.configStatus)
.delete("/api/admin/configs/:id", authenticated, controller.destroy) .delete("/api/admin/configs/:id", authenticated, controller.destroy)
.get("/api/admin/configs", authenticated, controller.fetch) .get("/api/admin/configs", authenticated, controller.fetch)
.get("/api/admin/configs/:id", authenticated, controller.find) .get("/api/admin/configs/:id", authenticated, controller.find)

View File

@ -1,19 +1,17 @@
const Router = require("@koa/router") const Router = require("@koa/router")
const { passport } = require("@budibase/auth") const { passport } = require("@budibase/auth")
const authController = require("../controllers/auth") const authController = require("../controllers/auth")
const context = require("koa/lib/context")
const router = Router() const router = Router()
router router
.post("/api/admin/auth", authController.authenticate) .post("/api/admin/auth", authController.authenticate)
.post("/api/admin/auth/logout", authController.logout)
.get("/api/auth/google", passport.authenticate("google"))
.get( .get(
"/api/auth/google/callback", "/api/admin/auth/google",
passport.authenticate("google", { passport.authenticate("google", { scope: ["profile", "email"] })
successRedirect: "/app",
failureRedirect: "/",
})
) )
.get("/api/admin/auth/google/callback", authController.googleAuth)
.post("/api/admin/auth/logout", authController.logout)
module.exports = router module.exports = router