mirror of
https://gitlab.com/commento/commento.git
synced 2025-06-29 22:56:37 -04:00
frontend, api: allow custom emails as commenters
I'm really sorry this came out as one huge commit, but I'm afraid I can't split this up. Closes https://gitlab.com/commento/commento-ce/issues/9
This commit is contained in:
@ -8,7 +8,7 @@ import (
|
||||
func TestCommentApproveBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
|
||||
|
||||
commentHex, _ := commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())
|
||||
|
||||
|
@ -151,5 +151,6 @@ func commentListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
"requireIdentification": d.RequireIdentification,
|
||||
"isFrozen": d.State == "frozen",
|
||||
"isModerator": isModerator,
|
||||
"configuredOauths": configuredOauths,
|
||||
})
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
func TestCommentListBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
|
||||
|
||||
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())
|
||||
commentNew(commenterHex, "example.com", "/path.html", "root", "**bar**", "approved", time.Now().UTC())
|
||||
@ -65,7 +65,7 @@ func TestCommentListEmpty(t *testing.T) {
|
||||
func TestCommentListSelfUnapproved(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "http://example.com/photo.jpg", "google", "")
|
||||
|
||||
commentNew(commenterHex, "example.com", "/path.html", "root", "**foo**", "unapproved", time.Now().UTC())
|
||||
|
||||
|
@ -116,5 +116,5 @@ func commentNewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
writeBody(w, response{"success": true, "commentHex": commentHex})
|
||||
writeBody(w, response{"success": true, "commentHex": commentHex, "approved": state == "approved"})
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
func TestCommentVoteBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
cr0, _ := commenterNew("test1@example.com", "Test1", "undefined", "http://example.com/photo.jpg", "google")
|
||||
cr1, _ := commenterNew("test2@example.com", "Test2", "undefined", "http://example.com/photo.jpg", "google")
|
||||
cr2, _ := commenterNew("test3@example.com", "Test3", "undefined", "http://example.com/photo.jpg", "google")
|
||||
cr0, _ := commenterNew("test1@example.com", "Test1", "undefined", "http://example.com/photo.jpg", "google", "")
|
||||
cr1, _ := commenterNew("test2@example.com", "Test2", "undefined", "http://example.com/photo.jpg", "google", "")
|
||||
cr2, _ := commenterNew("test3@example.com", "Test3", "undefined", "http://example.com/photo.jpg", "google", "")
|
||||
|
||||
c0, _ := commentNew(cr0, "example.com", "/path.html", "root", "**foo**", "approved", time.Now().UTC())
|
||||
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
func TestCommenterGetByHexBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
|
||||
|
||||
c, err := commenterGetByHex(commenterHex)
|
||||
if err != nil {
|
||||
@ -33,7 +33,7 @@ func TestCommenterGetByHexEmpty(t *testing.T) {
|
||||
func TestCommenterGetBySession(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
|
||||
|
||||
session, _ := commenterSessionNew()
|
||||
|
||||
@ -63,7 +63,7 @@ func TestCommenterGetBySessionEmpty(t *testing.T) {
|
||||
func TestCommenterGetByName(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
|
||||
|
||||
session, _ := commenterSessionNew()
|
||||
|
||||
|
71
api/commenter_login.go
Normal file
71
api/commenter_login.go
Normal file
@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func commenterLogin(email string, password string) (string, error) {
|
||||
if email == "" || password == "" {
|
||||
return "", errorMissingField
|
||||
}
|
||||
|
||||
statement := `
|
||||
SELECT commenterHex, passwordHash
|
||||
FROM commenters
|
||||
WHERE email = $1 AND provider = 'commento';
|
||||
`
|
||||
row := db.QueryRow(statement, email)
|
||||
|
||||
var commenterHex string
|
||||
var passwordHash string
|
||||
if err := row.Scan(&commenterHex, &passwordHash); err != nil {
|
||||
return "", errorInvalidEmailPassword
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(password)); err != nil {
|
||||
// TODO: is this the only possible error?
|
||||
return "", errorInvalidEmailPassword
|
||||
}
|
||||
|
||||
session, err := randomHex(32)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot create session hex: %v", err)
|
||||
return "", errorInternal
|
||||
}
|
||||
|
||||
statement = `
|
||||
INSERT INTO
|
||||
commenterSessions (session, commenterHex, creationDate)
|
||||
VALUES ($1, $2, $3 );
|
||||
`
|
||||
_, err = db.Exec(statement, session, commenterHex, time.Now().UTC())
|
||||
if err != nil {
|
||||
logger.Errorf("cannot insert session token: %v\n", err)
|
||||
return "", errorInternal
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func commenterLoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
type request struct {
|
||||
Email *string `json:"email"`
|
||||
Password *string `json:"password"`
|
||||
}
|
||||
|
||||
var x request
|
||||
if err := unmarshalBody(r, &x); err != nil {
|
||||
writeBody(w, response{"success": false, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
session, err := commenterLogin(*x.Email, *x.Password)
|
||||
if err != nil {
|
||||
writeBody(w, response{"success": false, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeBody(w, response{"success": true, "session": session})
|
||||
}
|
58
api/commenter_login_test.go
Normal file
58
api/commenter_login_test.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommenterLoginBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
if _, err := commenterLogin("test@example.com", "hunter2"); err == nil {
|
||||
t.Errorf("expected error not found when logging in without creating an account")
|
||||
return
|
||||
}
|
||||
|
||||
commenterNew("test@example.com", "Test", "undefined", "undefined", "commento", "hunter2")
|
||||
|
||||
if _, err := commenterLogin("test@example.com", "hunter2"); err != nil {
|
||||
t.Errorf("unexpected error when logging in: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := commenterLogin("test@example.com", "h******"); err == nil {
|
||||
t.Errorf("expected error not found when given wrong password")
|
||||
return
|
||||
}
|
||||
|
||||
if session, err := commenterLogin("test@example.com", "hunter2"); session == "" {
|
||||
t.Errorf("empty session on successful login: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommenterLoginEmpty(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
if _, err := commenterLogin("test@example.com", ""); err == nil {
|
||||
t.Errorf("expected error not found when passing empty password")
|
||||
return
|
||||
}
|
||||
|
||||
commenterNew("test@example.com", "Test", "undefined", "", "commenter", "hunter2")
|
||||
|
||||
if _, err := commenterLogin("test@example.com", ""); err == nil {
|
||||
t.Errorf("expected error not found when passing empty password")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommenterLoginNonCommento(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterNew("test@example.com", "Test", "undefined", "undefined", "google", "")
|
||||
|
||||
if _, err := commenterLogin("test@example.com", "hunter2"); err == nil {
|
||||
t.Errorf("expected error not found logging into a non-Commento account")
|
||||
return
|
||||
}
|
||||
}
|
@ -1,28 +1,74 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func commenterNew(email string, name string, link string, photo string, provider string) (string, error) {
|
||||
func commenterNew(email string, name string, link string, photo string, provider string, password string) (string, error) {
|
||||
if email == "" || name == "" || link == "" || photo == "" || provider == "" {
|
||||
return "", errorMissingField
|
||||
}
|
||||
|
||||
if provider == "commento" && password == "" {
|
||||
return "", errorMissingField
|
||||
}
|
||||
|
||||
if _, err := commenterGetByEmail(provider, email); err == nil {
|
||||
return "", errorEmailAlreadyExists
|
||||
}
|
||||
|
||||
commenterHex, err := randomHex(32)
|
||||
if err != nil {
|
||||
return "", errorInternal
|
||||
}
|
||||
|
||||
passwordHash := []byte{}
|
||||
if (password != "") {
|
||||
passwordHash, err = bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
logger.Errorf("cannot generate hash from password: %v\n", err)
|
||||
return "", errorInternal
|
||||
}
|
||||
}
|
||||
|
||||
statement := `
|
||||
INSERT INTO
|
||||
commenters (commenterHex, email, name, link, photo, provider, joinDate)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7 );
|
||||
commenters (commenterHex, email, name, link, photo, provider, passwordHash, joinDate)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8 );
|
||||
`
|
||||
_, err = db.Exec(statement, commenterHex, email, name, link, photo, provider, time.Now().UTC())
|
||||
_, err = db.Exec(statement, commenterHex, email, name, link, photo, provider, string(passwordHash), time.Now().UTC())
|
||||
if err != nil {
|
||||
logger.Errorf("cannot insert commenter: %v", err)
|
||||
return "", errorInternal
|
||||
}
|
||||
|
||||
return commenterHex, nil
|
||||
}
|
||||
|
||||
|
||||
func commenterNewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
type request struct {
|
||||
Email *string `json:"email"`
|
||||
Name *string `json:"name"`
|
||||
Website *string `json:"website"`
|
||||
Password *string `json:"password"`
|
||||
}
|
||||
|
||||
var x request
|
||||
if err := unmarshalBody(r, &x); err != nil {
|
||||
writeBody(w, response{"success": false, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: add gravatar?
|
||||
// TODO: email confirmation if provider = commento?
|
||||
// TODO: email confirmation if provider = commento?
|
||||
if _, err := commenterNew(*x.Email, *x.Name, *x.Website, "undefined", "commento", *x.Password); err != nil {
|
||||
writeBody(w, response{"success": false, "message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
writeBody(w, response{"success": true, "confirmEmail": smtpConfigured})
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
func TestCommenterNewBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
if _, err := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google"); err != nil {
|
||||
if _, err := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", ""); err != nil {
|
||||
t.Errorf("unexpected error creating new commenter: %v", err)
|
||||
return
|
||||
}
|
||||
@ -16,13 +16,22 @@ func TestCommenterNewBasics(t *testing.T) {
|
||||
func TestCommenterNewEmpty(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
if _, err := commenterNew("", "Test", "undefined", "https://example.com/photo.jpg", "google"); err == nil {
|
||||
if _, err := commenterNew("", "Test", "undefined", "https://example.com/photo.jpg", "google", ""); err == nil {
|
||||
t.Errorf("expected error not found creating new commenter with empty email")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := commenterNew("", "", "", "", ""); err == nil {
|
||||
if _, err := commenterNew("", "", "", "", "", ""); err == nil {
|
||||
t.Errorf("expected error not found creating new commenter with empty everything")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommenterNewCommento(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
if _, err := commenterNew("test@example.com", "Test", "undefined", "", "commento", ""); err == nil {
|
||||
t.Errorf("expected error not found creating new commento account with empty password")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
func TestCommenterSessionGetBasics(t *testing.T) {
|
||||
failTestOnError(t, setupTestEnv())
|
||||
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google")
|
||||
commenterHex, _ := commenterNew("test@example.com", "Test", "undefined", "https://example.com/photo.jpg", "google", "")
|
||||
|
||||
session, _ := commenterSessionNew()
|
||||
|
||||
|
@ -29,8 +29,8 @@ func parseConfig() error {
|
||||
"SMTP_PORT": "",
|
||||
"SMTP_FROM_ADDRESS": "",
|
||||
|
||||
"OAUTH_GOOGLE_KEY": "",
|
||||
"OAUTH_GOOGLE_SECRET": "",
|
||||
"GOOGLE_KEY": "",
|
||||
"GOOGLE_SECRET": "",
|
||||
}
|
||||
|
||||
for key, value := range defaults {
|
||||
|
@ -2,7 +2,11 @@ package main
|
||||
|
||||
import ()
|
||||
|
||||
var configuredOauths []string
|
||||
|
||||
func oauthConfigure() error {
|
||||
configuredOauths = []string{}
|
||||
|
||||
if err := googleOauthConfigure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -37,5 +37,7 @@ func googleOauthConfigure() error {
|
||||
Endpoint: google.Endpoint,
|
||||
}
|
||||
|
||||
configuredOauths = append(configuredOauths, "google");
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func googleCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
link = "undefined"
|
||||
}
|
||||
|
||||
commenterHex, err = commenterNew(email, user["name"].(string), link, user["picture"].(string), "google")
|
||||
commenterHex, err = commenterNew(email, user["name"].(string), link, user["picture"].(string), "google", "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Error: %s", err.Error())
|
||||
return
|
||||
|
@ -21,6 +21,8 @@ func initAPIRouter(router *mux.Router) error {
|
||||
router.HandleFunc("/api/domain/statistics", domainStatisticsHandler).Methods("POST")
|
||||
|
||||
router.HandleFunc("/api/commenter/session/new", commenterSessionNewHandler).Methods("GET")
|
||||
router.HandleFunc("/api/commenter/new", commenterNewHandler).Methods("POST")
|
||||
router.HandleFunc("/api/commenter/login", commenterLoginHandler).Methods("POST")
|
||||
router.HandleFunc("/api/commenter/self", commenterSelfHandler).Methods("POST")
|
||||
|
||||
router.HandleFunc("/api/oauth/google/redirect", googleRedirectHandler).Methods("GET")
|
||||
|
Reference in New Issue
Block a user