mirror of https://github.com/writeas/writefreely
Merge pull request #232 from writeas/T712-oauth-registration-improvements
OAuth registration improvements Resolves T712pull/236/head
commit
a2a9f60976
@ -0,0 +1,206 @@ |
||||
package writefreely |
||||
|
||||
import ( |
||||
"crypto/sha256" |
||||
"encoding/hex" |
||||
"fmt" |
||||
"github.com/writeas/impart" |
||||
"github.com/writeas/web-core/auth" |
||||
"github.com/writeas/web-core/log" |
||||
"github.com/writeas/writefreely/page" |
||||
"html/template" |
||||
"net/http" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
type viewOauthSignupVars struct { |
||||
page.StaticPage |
||||
To string |
||||
Message template.HTML |
||||
Flashes []template.HTML |
||||
|
||||
AccessToken string |
||||
TokenUsername string |
||||
TokenAlias string |
||||
TokenEmail string |
||||
TokenRemoteUser string |
||||
Provider string |
||||
ClientID string |
||||
TokenHash string |
||||
|
||||
Username string |
||||
Alias string |
||||
Email string |
||||
} |
||||
|
||||
const ( |
||||
oauthParamAccessToken = "access_token" |
||||
oauthParamTokenUsername = "token_username" |
||||
oauthParamTokenAlias = "token_alias" |
||||
oauthParamTokenEmail = "token_email" |
||||
oauthParamTokenRemoteUserID = "token_remote_user" |
||||
oauthParamClientID = "client_id" |
||||
oauthParamProvider = "provider" |
||||
oauthParamHash = "signature" |
||||
oauthParamUsername = "username" |
||||
oauthParamAlias = "alias" |
||||
oauthParamEmail = "email" |
||||
oauthParamPassword = "password" |
||||
) |
||||
|
||||
type oauthSignupPageParams struct { |
||||
AccessToken string |
||||
TokenUsername string |
||||
TokenAlias string |
||||
TokenEmail string |
||||
TokenRemoteUser string |
||||
ClientID string |
||||
Provider string |
||||
TokenHash string |
||||
} |
||||
|
||||
func (p oauthSignupPageParams) HashTokenParams(key string) string { |
||||
hasher := sha256.New() |
||||
hasher.Write([]byte(key)) |
||||
hasher.Write([]byte(p.AccessToken)) |
||||
hasher.Write([]byte(p.TokenUsername)) |
||||
hasher.Write([]byte(p.TokenAlias)) |
||||
hasher.Write([]byte(p.TokenEmail)) |
||||
hasher.Write([]byte(p.TokenRemoteUser)) |
||||
hasher.Write([]byte(p.ClientID)) |
||||
hasher.Write([]byte(p.Provider)) |
||||
return hex.EncodeToString(hasher.Sum(nil)) |
||||
} |
||||
|
||||
func (h oauthHandler) viewOauthSignup(app *App, w http.ResponseWriter, r *http.Request) error { |
||||
tp := &oauthSignupPageParams{ |
||||
AccessToken: r.FormValue(oauthParamAccessToken), |
||||
TokenUsername: r.FormValue(oauthParamTokenUsername), |
||||
TokenAlias: r.FormValue(oauthParamTokenAlias), |
||||
TokenEmail: r.FormValue(oauthParamTokenEmail), |
||||
TokenRemoteUser: r.FormValue(oauthParamTokenRemoteUserID), |
||||
ClientID: r.FormValue(oauthParamClientID), |
||||
Provider: r.FormValue(oauthParamProvider), |
||||
} |
||||
if tp.HashTokenParams(h.Config.Server.HashSeed) != r.FormValue(oauthParamHash) { |
||||
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Request has been tampered with."} |
||||
} |
||||
tp.TokenHash = tp.HashTokenParams(h.Config.Server.HashSeed) |
||||
if err := h.validateOauthSignup(r); err != nil { |
||||
return h.showOauthSignupPage(app, w, r, tp, err) |
||||
} |
||||
|
||||
hashedPass, err := auth.HashPass([]byte(r.FormValue(oauthParamPassword))) |
||||
if err != nil { |
||||
return h.showOauthSignupPage(app, w, r, tp, fmt.Errorf("unable to hash password")) |
||||
} |
||||
newUser := &User{ |
||||
Username: r.FormValue(oauthParamUsername), |
||||
HashedPass: hashedPass, |
||||
HasPass: true, |
||||
Email: prepareUserEmail(r.FormValue(oauthParamEmail), h.EmailKey), |
||||
Created: time.Now().Truncate(time.Second).UTC(), |
||||
} |
||||
displayName := r.FormValue(oauthParamAlias) |
||||
if len(displayName) == 0 { |
||||
displayName = r.FormValue(oauthParamUsername) |
||||
} |
||||
|
||||
err = h.DB.CreateUser(h.Config, newUser, displayName) |
||||
if err != nil { |
||||
return h.showOauthSignupPage(app, w, r, tp, err) |
||||
} |
||||
|
||||
err = h.DB.RecordRemoteUserID(r.Context(), newUser.ID, r.FormValue(oauthParamTokenRemoteUserID), r.FormValue(oauthParamProvider), r.FormValue(oauthParamClientID), r.FormValue(oauthParamAccessToken)) |
||||
if err != nil { |
||||
return h.showOauthSignupPage(app, w, r, tp, err) |
||||
} |
||||
|
||||
if err := loginOrFail(h.Store, w, r, newUser); err != nil { |
||||
return h.showOauthSignupPage(app, w, r, tp, err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (h oauthHandler) validateOauthSignup(r *http.Request) error { |
||||
username := r.FormValue(oauthParamUsername) |
||||
if len(username) < h.Config.App.MinUsernameLen { |
||||
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Username is too short."} |
||||
} |
||||
if len(username) > 100 { |
||||
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Username is too long."} |
||||
} |
||||
alias := r.FormValue(oauthParamAlias) |
||||
if len(alias) == 0 { |
||||
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Alias is too short."} |
||||
} |
||||
password := r.FormValue("password") |
||||
if len(password) == 0 { |
||||
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Password is too short."} |
||||
} |
||||
email := r.FormValue(oauthParamEmail) |
||||
if len(email) > 0 { |
||||
parts := strings.Split(email, "@") |
||||
if len(parts) != 2 || (len(parts[0]) < 1 || len(parts[1]) < 1) { |
||||
return impart.HTTPError{Status: http.StatusBadRequest, Message: "Invalid email address"} |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (h oauthHandler) showOauthSignupPage(app *App, w http.ResponseWriter, r *http.Request, tp *oauthSignupPageParams, errMsg error) error { |
||||
username := tp.TokenUsername |
||||
alias := tp.TokenAlias |
||||
email := tp.TokenEmail |
||||
|
||||
session, err := app.sessionStore.Get(r, cookieName) |
||||
if err != nil { |
||||
// Ignore this
|
||||
log.Error("Unable to get session; ignoring: %v", err) |
||||
} |
||||
|
||||
if tmpValue := r.FormValue(oauthParamUsername); len(tmpValue) > 0 { |
||||
username = tmpValue |
||||
} |
||||
if tmpValue := r.FormValue(oauthParamAlias); len(tmpValue) > 0 { |
||||
alias = tmpValue |
||||
} |
||||
if tmpValue := r.FormValue(oauthParamEmail); len(tmpValue) > 0 { |
||||
email = tmpValue |
||||
} |
||||
|
||||
p := &viewOauthSignupVars{ |
||||
StaticPage: pageForReq(app, r), |
||||
To: r.FormValue("to"), |
||||
Flashes: []template.HTML{}, |
||||
|
||||
AccessToken: tp.AccessToken, |
||||
TokenUsername: tp.TokenUsername, |
||||
TokenAlias: tp.TokenAlias, |
||||
TokenEmail: tp.TokenEmail, |
||||
TokenRemoteUser: tp.TokenRemoteUser, |
||||
Provider: tp.Provider, |
||||
ClientID: tp.ClientID, |
||||
TokenHash: tp.TokenHash, |
||||
|
||||
Username: username, |
||||
Alias: alias, |
||||
Email: email, |
||||
} |
||||
|
||||
// Display any error messages
|
||||
flashes, _ := getSessionFlashes(app, w, r, session) |
||||
for _, flash := range flashes { |
||||
p.Flashes = append(p.Flashes, template.HTML(flash)) |
||||
} |
||||
if errMsg != nil { |
||||
p.Flashes = append(p.Flashes, template.HTML(errMsg.Error())) |
||||
} |
||||
err = pages["signup-oauth.tmpl"].ExecuteTemplate(w, "base", p) |
||||
if err != nil { |
||||
log.Error("Unable to render signup-oauth: %v", err) |
||||
return err |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,118 @@ |
||||
{{define "head"}}<title>Log in — {{.SiteName}}</title> |
||||
<meta name="description" content="Log in to {{.SiteName}}."> |
||||
<meta itemprop="description" content="Log in to {{.SiteName}}."> |
||||
<style>input{margin-bottom:0.5em;}</style> |
||||
<style type="text/css"> |
||||
h2 { |
||||
font-weight: normal; |
||||
} |
||||
#pricing.content-container div.form-container #payment-form { |
||||
display: block !important; |
||||
} |
||||
#pricing #signup-form table { |
||||
max-width: inherit !important; |
||||
width: 100%; |
||||
} |
||||
#pricing #payment-form table { |
||||
margin-top: 0 !important; |
||||
max-width: inherit !important; |
||||
width: 100%; |
||||
} |
||||
tr.subscription { |
||||
border-spacing: 0; |
||||
} |
||||
#pricing.content-container tr.subscription button { |
||||
margin-top: 0 !important; |
||||
margin-bottom: 0 !important; |
||||
width: 100%; |
||||
} |
||||
#pricing tr.subscription td { |
||||
padding: 0 0.5em; |
||||
} |
||||
#pricing table.billing > tbody > tr > td:first-child { |
||||
vertical-align: middle !important; |
||||
} |
||||
.billing-section { |
||||
display: none; |
||||
} |
||||
.billing-section.bill-me { |
||||
display: table-row; |
||||
} |
||||
#btn-create { |
||||
color: white !important; |
||||
} |
||||
#total-price { |
||||
padding-left: 0.5em; |
||||
} |
||||
#alias-site.demo { |
||||
color: #999; |
||||
} |
||||
#alias-site { |
||||
text-align: left; |
||||
margin: 0.5em 0; |
||||
} |
||||
form dd { |
||||
margin: 0; |
||||
} |
||||
</style> |
||||
{{end}} |
||||
{{define "content"}} |
||||
<div id="pricing" class="tight content-container"> |
||||
<h1>Log in to {{.SiteName}}</h1> |
||||
|
||||
{{if .Flashes}}<ul class="errors"> |
||||
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}} |
||||
</ul>{{end}} |
||||
|
||||
<div id="billing"> |
||||
<form action="/oauth/signup" method="post" style="text-align: center;margin-top:1em;" onsubmit="disableSubmit()"> |
||||
<input type="hidden" name="access_token" value="{{ .AccessToken }}" /> |
||||
<input type="hidden" name="token_username" value="{{ .TokenUsername }}" /> |
||||
<input type="hidden" name="token_alias" value="{{ .TokenAlias }}" /> |
||||
<input type="hidden" name="token_email" value="{{ .TokenEmail }}" /> |
||||
<input type="hidden" name="token_remote_user" value="{{ .TokenRemoteUser }}" /> |
||||
<input type="hidden" name="provider" value="{{ .Provider }}" /> |
||||
<input type="hidden" name="client_id" value="{{ .ClientID }}" /> |
||||
<input type="hidden" name="signature" value="{{ .TokenHash }}" /> |
||||
|
||||
<dl class="billing"> |
||||
<label> |
||||
<dt>Blog Title</dt> |
||||
<dd> |
||||
<input type="text" style="width: 100%; box-sizing: border-box;" name="alias" placeholder="Alias"{{ if .Alias }} value="{{.Alias}}"{{ end }} /> |
||||
</dd> |
||||
</label> |
||||
<label> |
||||
<dt>Username</dt> |
||||
<dd> |
||||
<input type="text" name="username" style="width: 100%; box-sizing: border-box;" placeholder="Username" value="{{.Username}}" /><br /> |
||||
{{if .Federation}}<p id="alias-site" class="demo">@<strong>your-username</strong>@{{.FriendlyHost}}</p>{{else}}<p id="alias-site" class="demo">{{.FriendlyHost}}/<strong>your-username</strong></p>{{end}} |
||||
</dd> |
||||
</label> |
||||
<label> |
||||
<dt>Email</dt> |
||||
<dd> |
||||
<input type="text" name="email" style="width: 100%; box-sizing: border-box;" placeholder="Email"{{ if .Email }} value="{{.Email}}"{{ end }} /> |
||||
</dd> |
||||
</label> |
||||
<label> |
||||
<dt>Password</dt> |
||||
<dd> |
||||
<input type="password" name="password" style="width: 100%; box-sizing: border-box;" placeholder="Password" /><br /> |
||||
</dd> |
||||
</label> |
||||
<dt> |
||||
<input type="submit" id="btn-login" value="Login" /> |
||||
</dt> |
||||
</dl> |
||||
</form> |
||||
</div> |
||||
|
||||
<script type="text/javascript"> |
||||
function disableSubmit() { |
||||
var $btn = document.getElementById("btn-login"); |
||||
$btn.value = "Logging in..."; |
||||
$btn.disabled = true; |
||||
} |
||||
</script> |
||||
{{end}} |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.0 KiB |
Loading…
Reference in new issue