Skip to content
Permalink
Browse files

feat: google sign-in

  • Loading branch information...
movsb committed Mar 30, 2019
1 parent 025515c commit 585c8d078616950d66e24150277d94fda75b0ceb
Showing with 169 additions and 32 deletions.
  1. +26 −6 admin/admin.go
  2. +68 −11 admin/templates/login.html
  3. +36 −12 auth/auth.go
  4. +1 −0 go.mod
  5. +2 −0 go.sum
  6. +10 −3 setup/init/main.go
  7. +1 −0 setup/migration/list.go
  8. +25 −0 setup/migration/versions.go
@@ -54,7 +54,8 @@ func (d *AdminFooterData) FooterHook() string {
}

type LoginData struct {
Redirect string
Redirect string
GoogleClientID string
}

type AdminIndexData struct {
@@ -216,7 +217,8 @@ func (a *Admin) queryLogin(c *gin.Context) {
}

d := LoginData{
Redirect: redirect,
Redirect: redirect,
GoogleClientID: a.auth.GoogleClientID,
}

a.render(c.Writer, "login", &d)
@@ -228,12 +230,30 @@ func (a *Admin) queryLogout(c *gin.Context) {
}

func (a *Admin) postLogin(c *gin.Context) {
username := c.PostForm("user")
password := c.PostForm("passwd")
redirect := c.PostForm("redirect")
if a.auth.AuthLogin(username, password) {
success := false

typ := c.DefaultQuery("type", "basic")
switch typ {
case "basic":
username := c.PostForm("user")
password := c.PostForm("passwd")
success = a.auth.AuthLogin(username, password)
case "google":
token := c.PostForm("token")
fmt.Println("收到:", token)
success = !a.auth.AuthGoogle(token).IsGuest()
}

if success {
a.auth.MakeCookie(c)
c.Redirect(302, redirect)
if typ == "google" {
c.JSON(200, gin.H{
"redirect": redirect,
})
} else {
c.Redirect(302, redirect)
}
} else {
c.Redirect(302, c.Request.URL.String())
}
@@ -24,7 +24,7 @@
left: 0;
top: 50%;
right: 0;
width: 220px;
width: 360px;
margin: auto;
border: 1px solid gray;
transform: translateY(-50%);
@@ -33,12 +33,13 @@
#title {
font-size: 20px;
text-align: center;
height: 3em;
line-height: 3em;
line-height: 2.5em;
border-bottom: 1px solid gray;
}
#input-wrapper {
padding: 0 1.5em 1em 1.5em;
padding: 1em;
display: flex;
}
.input {
@@ -55,21 +56,77 @@
font-size: 1em;
}
.right {
margin-left: 1em;
}
</style>
<script>
window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
alert("Error occured: " + errorMsg);//or any message
return false;
};
</script>
{{ if .GoogleClientID }}
<meta name="google-signin-scope" content="profile email">
<meta name="google-signin-client_id" content="{{.GoogleClientID}}.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>
{{ end }}
</head>
<body>
<div id="wrapper">
<form method="post" id="login-form">
<div id="title">登录</div>
<div id="input-wrapper">
<div>
<input class="input" type="text" name="user" placeholder="用户名" />
<input class="input" type="password" name="passwd" placeholder="密码" />
</div>
<div class="submit" style="text-align: right;">
<input class="btn" type="submit" value="登录" />
<input class="btn" type="button" value="取消" onclick="location.href='/';" />
<div class=left>
<div>
<input class="input" type="text" name="user" placeholder="用户名" />
<input class="input" type="password" name="passwd" placeholder="密码" />
</div>
<div class="submit" style="text-align: right;">
<input class="btn" type="submit" value="登录" />
<input class="btn" type="button" value="取消" onclick="location.href='/';" />
</div>
</div>
{{ if .GoogleClientID }}
<div class=right>
<div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
<script>
function onSignIn(googleUser) {
// Useful data for your client-side scripts:
var profile = googleUser.getBasicProfile();
console.log("ID: " + profile.getId()); // Don't send this directly to your server!
console.log('Full Name: ' + profile.getName());
console.log('Given Name: ' + profile.getGivenName());
console.log('Family Name: ' + profile.getFamilyName());
console.log("Image URL: " + profile.getImageUrl());
console.log("Email: " + profile.getEmail());
// The ID token you need to pass to your backend:
var id_token = googleUser.getAuthResponse().id_token;
console.log("ID Token: " + id_token);
var data = new FormData();
data.append("token", id_token);
var xhr = new XMLHttpRequest();
xhr.open("POST", location.pathname + "?type=google");
xhr.onload = function() {
console.log(xhr);
if(xhr.readyState == 4 && xhr.status == 200) {
var auth2 = gapi.auth2.getAuthInstance();
auth2.signOut().then(function () {
console.log('User signed out.');
var data = JSON.parse(xhr.responseText);
location.href = data.redirect;
});
}
};
console.log("发送TOKEN:", id_token);
xhr.send(data);
}
</script>
</div>
{{ end }}
</div>
<div class="hidden">
<input type="hidden" name="redirect" value="{{.Redirect}}" />
@@ -3,10 +3,11 @@ package auth
import (
"context"
"crypto/sha1"
"encoding/json"
"fmt"
"strings"

"github.com/gin-gonic/gin"
googleidtokenverifier "github.com/movsb/google-idtoken-verifier"
)

type ctxAuthKey struct{}
@@ -35,28 +36,39 @@ func (u *User) Context(parent context.Context) context.Context {
return context.WithValue(parent, ctxAuthKey{}, AuthContext{u})
}

func CreateLogin(username, password string) string {
return username + "," + fmt.Sprintf("%x", sha1.Sum([]byte(password)))
func HashPassword(password string) string {
return fmt.Sprintf("%x", sha1.Sum([]byte(password)))
}

type Auth struct {
savedUser string
savedPassword string
key string
key string
SavedAuth
}

type SavedAuth struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
GoogleClientID string `json:"google_client_id,omitempty"`
AdminGoogleID string `json:"admin_google_id,omitempty"`
}

func (a SavedAuth) Encode() string {
bys, _ := json.Marshal(&a)
return string(bys)
}

func (o *Auth) SetKey(key string) {
o.key = key
}

func (o *Auth) SetLogin(login string) {
ss := strings.SplitN(login, ",", 2)
o.savedUser = ss[0]
o.savedPassword = ss[1]
if err := json.Unmarshal([]byte(login), &o.SavedAuth); err != nil {
panic(err)
}
}

func (o *Auth) Login() string {
return o.savedUser + "," + o.savedPassword
return o.Username + "," + o.Password
}

func (*Auth) sha1(in string) string {
@@ -65,8 +77,8 @@ func (*Auth) sha1(in string) string {
}

func (o *Auth) AuthLogin(username string, password string) bool {
if username == o.savedUser {
if o.sha1(password) == o.savedPassword {
if username == o.Username {
if o.sha1(password) == o.Password {
return true
}
}
@@ -99,6 +111,18 @@ func (o *Auth) AuthHeader(c *gin.Context) *User {
return guest
}

func (o *Auth) AuthGoogle(token string) *User {
fullClientID := o.GoogleClientID + ".apps.googleusercontent.com"
claims, err := googleidtokenverifier.Verify(token, fullClientID)
if err != nil {
return guest
}
if claims.Sub == o.AdminGoogleID {
return admin
}
return guest
}

func (a *Auth) AuthContext(ctx context.Context) *User {
if value, ok := ctx.Value(ctxAuthKey{}).(AuthContext); ok {
return value.User
1 go.mod
@@ -9,6 +9,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/movsb/alioss v0.0.0-20180411084708-ae700d1e4460
github.com/movsb/google-idtoken-verifier v0.0.0-20190329202541-1a6aa2c7e316
github.com/movsb/taorm v0.0.0-20190320113140-0d1003a5a06c
github.com/stretchr/testify v1.3.0 // indirect
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2 // indirect
2 go.sum
@@ -18,6 +18,8 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/movsb/alioss v0.0.0-20180411084708-ae700d1e4460 h1:tbHbJHFHHsfOHiWHTTQyq0d1bSubUWFgRNF84zRv/mE=
github.com/movsb/alioss v0.0.0-20180411084708-ae700d1e4460/go.mod h1:1uvxVt9wTuHYsiwYurvgRoq4rTtRwzojc03U1agxL5o=
github.com/movsb/google-idtoken-verifier v0.0.0-20190329202541-1a6aa2c7e316 h1:eeMZo/pDQ2Vdmq7jFlb3JMYDLHtQWELZIw+oeEevh4A=
github.com/movsb/google-idtoken-verifier v0.0.0-20190329202541-1a6aa2c7e316/go.mod h1:yZbOV1cKd0igi4wBBcVrs4G2ooTWhjxUT9pYV5RDN44=
github.com/movsb/taorm v0.0.0-20190320113140-0d1003a5a06c h1:0KbOq+C8Wmqk/HfdIyt/v695ZX7a1Cn6Vv+3ZeFYX20=
github.com/movsb/taorm v0.0.0-20190320113140-0d1003a5a06c/go.mod h1:4bNL/BYCSif17tS1JDwvDlpEfJsa/hGaxWsOqmEx0S8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -11,7 +11,7 @@ import (
"github.com/movsb/taorm"
)

const dbVer = 6
const dbVer = 7

var liner = NewStdinLineReader()

@@ -74,9 +74,16 @@ func createDatabaseTables(db *taorm.DB, dbName string) {
func createBlogUser(db *taorm.DB) {
blogUsername := liner.MustReadLine("博客用户名:")
blogPassword := liner.MustReadLine("博客密码:")
login := auth.CreateLogin(blogUsername, blogPassword)
googleClientID := liner.MustReadLine("谷歌ClientID:")
adminGoogleID := liner.MustReadLine("管理员谷歌ID:")
savedAuth := auth.SavedAuth{
Username: blogUsername,
Password: auth.HashPassword(blogPassword),
GoogleClientID: googleClientID,
AdminGoogleID: adminGoogleID,
}
query := fmt.Sprintf("INSERT INTO options (name,value) VALUES (?,?)")
db.MustExec(query, "login", login)
db.MustExec(query, "login", savedAuth.Encode())
}

func createBlogInfo(db *taorm.DB) {
@@ -18,4 +18,5 @@ var gVersions = []VersionUpdater{
{4, v4},
{5, v5},
{6, v6},
{7, v7},
}
@@ -3,6 +3,9 @@ package migration
import (
"database/sql"
"encoding/json"
"strings"

"github.com/movsb/taoblog/auth"

"github.com/movsb/taorm"
)
@@ -108,3 +111,25 @@ func v6(tx *sql.Tx) {
}
}
}

func v7(tx *sql.Tx) {
var login string
query := "SELECT value FROM options WHERE name=?"
row := tx.QueryRow(query, "login")
if err := row.Scan(&login); err != nil {
panic(err)
}
parts := strings.Split(login, ",")
if len(parts) != 2 {
panic("invalid login value")
}
savedAuth := auth.SavedAuth{
Username: parts[0],
Password: parts[1],
}
login = savedAuth.Encode()
query = "UPDATE options SET value=? WHERE name=?"
if _, err := tx.Exec(query, login, "login"); err != nil {
panic(err)
}
}

0 comments on commit 585c8d0

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