Skip to content
Permalink
Browse files

Code, full login/logout tutorial

  • Loading branch information...
Depado committed Feb 14, 2019
1 parent aba931d commit 037aac3886c4ca30c2638ea03a4bf55db6dce520
@@ -0,0 +1,78 @@
package admin

import (
"net/http"
"path/filepath"

"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/qor/admin"

"github.com/Depado/articles/code/qor/models"
)

// Admin abstracts the whole QOR Admin + authentication process
type Admin struct {
db *gorm.DB
auth auth
adm *admin.Admin
adminpath string
prefix string
}

// New will create a new admin using the provided gorm connection, a prefix
// for the various routes. Prefix can be an empty string. The cookie secret
// will be used to encrypt/decrypt the cookie on the backend side.
func New(db *gorm.DB, prefix, cookiesecret string) *Admin {
adminpath := filepath.Join(prefix, "/admin")
a := Admin{
db: db,
prefix: prefix,
adminpath: adminpath,
auth: auth{
db: db,
paths: pathConfig{
admin: adminpath,
login: filepath.Join(prefix, "/login"),
logout: filepath.Join(prefix, "/logout"),
},
session: sessionConfig{
key: "userid",
name: "admsession",
store: cookie.NewStore([]byte(cookiesecret)),
},
},
}
a.adm = admin.New(&admin.AdminConfig{
SiteName: "My Admin Interface",
DB: db,
Auth: a.auth,
})
addUser(a.adm)
a.adm.AddResource(&models.Product{})
return &a
}

func (a Admin) Bind(r *gin.Engine) {
mux := http.NewServeMux()
a.adm.MountTo(a.adminpath, mux)

r.LoadHTMLGlob("admin/templates/*")

// logintpl, err := bindatafs.AssetFS.NameSpace("login").Asset("login.html")
// if err != nil {
// logrus.WithError(err).Fatal("Unable to find HTML template for login page in backoffice")
// }
// r.SetHTMLTemplate(template.Must(template.New("login").Parse(string(logintpl))))

g := r.Group(a.prefix)
g.Use(sessions.Sessions(a.auth.session.name, a.auth.session.store))
{
g.Any("/admin/*resources", gin.WrapH(mux))
g.GET("/login", a.auth.GetLogin)
g.POST("/login", a.auth.PostLogin)
g.GET("/logout", a.auth.GetLogout)
}
}
@@ -11,8 +11,6 @@ import (
"github.com/qor/admin"
"github.com/qor/qor"
"github.com/sirupsen/logrus"

"github.com/Depado/articles/code/qor/v1/models"
)

// Auth is a structure to handle authentication for QOR. It will satisify the
@@ -41,7 +39,7 @@ func (a *auth) GetLogin(c *gin.Context) {
c.Redirect(http.StatusSeeOther, a.paths.admin)
return
}
c.HTML(http.StatusOK, "login", gin.H{})
c.HTML(http.StatusOK, "login.html", gin.H{})
}

// PostLogin is the handler to check if the user can connect
@@ -53,8 +51,8 @@ func (a *auth) PostLogin(c *gin.Context) {
c.Redirect(http.StatusSeeOther, a.paths.login)
return
}
var u models.AdminUser
if a.db.Where(&models.AdminUser{Email: email}).First(&u).RecordNotFound() {
var u User
if a.db.Where(&User{Email: email}).First(&u).RecordNotFound() {
c.Redirect(http.StatusSeeOther, a.paths.login)
return
}
@@ -101,7 +99,7 @@ func (a auth) GetCurrentUser(c *admin.Context) qor.CurrentUser {
return nil
}

var user models.AdminUser
var user User
if !a.db.First(&user, "id = ?", userid).RecordNotFound() {
return &user
}
@@ -0,0 +1,12 @@
package resources

import (
"github.com/qor/admin"

"github.com/Depado/articles/code/qor/models"
)

// AddProduct adds the product model to the admin interface
func AddProduct(adm *admin.Admin) {
adm.AddResource(&models.Product{})
}
@@ -49,7 +49,6 @@
<input class="form-input" name="password" type="password" placeholder="Password">
<i class="form-icon icon icon-more-horiz"></i>
</div>

<button class="btn btn-primary input-group-btn">Submit</button>
</form>
</div>
@@ -0,0 +1,110 @@
package admin

import (
"fmt"
"time"

"github.com/jinzhu/gorm"
"github.com/qor/admin"
"github.com/qor/qor"
"github.com/qor/qor/resource"
"github.com/qor/validations"
"golang.org/x/crypto/bcrypt"
gormigrate "gopkg.in/gormigrate.v1"
)

// User defines how an admin user is represented in database
type User struct {
gorm.Model
Email string `gorm:"not null;unique"`
FirstName string
LastName string
Password []byte
LastLogin *time.Time
}

// TableName allows to override the name of the table
func (u User) TableName() string {
return "admin_users"
}

// DisplayName satisfies the interface for Qor Admin
func (u User) DisplayName() string {
if u.FirstName != "" && u.LastName != "" {
return fmt.Sprintf("%s %s", u.FirstName, u.LastName)
}
return u.Email
}

// HashPassword is a simple utility function to hash the password sent via API
// before inserting it in database
func (u *User) HashPassword() error {
pwd, err := bcrypt.GenerateFromPassword(u.Password, bcrypt.DefaultCost)
if err != nil {
return err
}
u.Password = pwd
return nil
}

// CheckPassword is a simple utility function to check the password given as raw
// against the user's hashed password
func (u User) CheckPassword(raw string) bool {
return bcrypt.CompareHashAndPassword(u.Password, []byte(raw)) == nil
}

// AdminUserMigration is the migration that creates our user model
var AdminUserMigration = &gormigrate.Migration{
ID: "init_admin",
Migrate: func(tx *gorm.DB) error {
var err error

type adminUser struct {
gorm.Model
Email string `gorm:"not null;unique"`
FirstName string
LastName string
Password []byte
LastLogin *time.Time
}

if err = tx.CreateTable(&adminUser{}).Error; err != nil {
return err
}
var pwd []byte
if pwd, err = bcrypt.GenerateFromPassword([]byte("changeme"), bcrypt.DefaultCost); err != nil {
return err
}
usr := adminUser{
Email: "you@yourcompany.com",
Password: pwd,
}
return tx.Save(&usr).Error
},
Rollback: func(tx *gorm.DB) error {
return tx.DropTable("admin_users").Error
},
}

func addUser(adm *admin.Admin) {
usr := adm.AddResource(&User{}, &admin.Config{Menu: []string{"User Management"}})
usr.IndexAttrs("-Password")
usr.Meta(&admin.Meta{
Name: "Password",
Type: "password",
Setter: func(resource interface{}, metaValue *resource.MetaValue, context *qor.Context) {
values := metaValue.Value.([]string)
if len(values) > 0 {
if np := values[0]; np != "" {
pwd, err := bcrypt.GenerateFromPassword([]byte(np), bcrypt.DefaultCost)
if err != nil {
context.DB.AddError(validations.NewError(usr, "Password", "Can't encrypt password")) // nolint: gosec,errcheck
return
}
u := resource.(*User)
u.Password = pwd
}
}
},
})
}
@@ -1,15 +1,13 @@
package main

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/qor/admin"
"github.com/sirupsen/logrus"

"github.com/Depado/articles/code/qor/v1/migrate"
"github.com/Depado/articles/code/qor/admin"
"github.com/Depado/articles/code/qor/migrate"
)

func main() {
@@ -26,11 +24,8 @@ func main() {
logrus.WithError(err).Fatal("Couldn't run migration")
}

adm := admin.New(&admin.AdminConfig{SiteName: "Admin", DB: db})
mux := http.NewServeMux()
adm.MountTo("/admin", mux)

r := gin.New()
r.Any("/admin/*resources", gin.WrapH(mux))
a := admin.New(db, "", "secret")
a.Bind(r)
r.Run("127.0.0.1:8080")
}
File renamed without changes.
@@ -3,13 +3,16 @@ package migrate
import (
"github.com/jinzhu/gorm"
"gopkg.in/gormigrate.v1"

"github.com/Depado/articles/code/qor/admin"
)

// Start starts the migration process
func Start(db *gorm.DB) error {
m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
uuidCheck,
initial,
admin.AdminUserMigration,
})
return m.Migrate()
}
File renamed without changes.
File renamed without changes.

This file was deleted.

This file was deleted.

0 comments on commit 037aac3

Please sign in to comment.
You can’t perform that action at this time.