Style, auth with file

This commit is contained in:
Meutel 2017-07-29 16:51:12 +02:00
parent ee2bfd95cb
commit 8ec59e4826
8 changed files with 204 additions and 75 deletions

View File

@ -1,31 +1,35 @@
package admin package admin
import ( import (
"crypto/hmac"
"crypto/sha256"
"errors"
"fmt"
"html/template" "html/template"
"io/ioutil"
"log"
"net/http" "net/http"
"os"
"path/filepath"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
) )
type AuthCookie struct { type AuthCookie struct {
Templates *template.Template Templates *template.Template
Store *sessions.CookieStore Store *sessions.CookieStore
DataDir *os.File
PasswordSecret string
} }
func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) { func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) {
formErr := make(map[string]string) formErr := make(map[string]error)
switch req.Method { switch req.Method {
case "POST": case "POST":
username := req.FormValue("username") username := req.FormValue("username")
if username == "" { formErr["username"] = app.VerifyUsername(username)
formErr["username"] = "Empty username" formErr["password"] = app.VerifyPassword(username, req.FormValue("password"), res, req)
} if formErr["username"] == nil && formErr["password"] == nil {
password := req.FormValue("password")
if password == "" {
formErr["password"] = "Empty password"
}
// FIXME verifiy password with file in data dir
if len(formErr) == 0 {
app.SaveUsername(username, res, req) app.SaveUsername(username, res, req)
RedirectHome(res, req) RedirectHome(res, req)
return return
@ -39,6 +43,44 @@ func (app *AuthCookie) LogoutPage(res http.ResponseWriter, req *http.Request) {
app.SaveUsername("", res, req) app.SaveUsername("", res, req)
RedirectHome(res, req) RedirectHome(res, req)
} }
func (app *AuthCookie) VerifyUsername(username string) error {
if username == "" {
return errors.New("Empty username")
}
return nil
}
func (app *AuthCookie) VerifyPassword(username, password string, res http.ResponseWriter, req *http.Request) error {
if password == "" {
return errors.New("Empty password")
}
passfile, err := os.Open(filepath.Join(app.DataDir.Name(), username, ".password"))
if err != nil {
log.Println("Cannot open password file", err)
return errors.New("Authentification failed")
}
defer passfile.Close()
if username == "" {
return nil
}
expected, err := ioutil.ReadAll(passfile)
if err != nil {
log.Println("Cannot read password file", err)
return errors.New("Authentification failed")
}
expectedStr := string(expected)
var expectedMAC []byte
fmt.Sscanf(expectedStr, "%x", &expectedMAC)
mac := hmac.New(sha256.New, []byte(app.PasswordSecret))
mac.Write([]byte(password))
passwordMAC := mac.Sum(nil)
if !hmac.Equal(passwordMAC, expectedMAC) {
log.Printf("Unmatched password for %s: %x - %x\n", username, passwordMAC, expectedMAC)
return errors.New("Authentification failed")
}
log.Printf("Authentification successful")
return nil
}
func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session { func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session {
session, _ := app.Store.Get(req, "session") session, _ := app.Store.Get(req, "session")
return session return session

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
@ -29,20 +28,17 @@ func main() {
} }
defer data.Close() defer data.Close()
admin := admin.AuthCookie{
Templates: tpl,
Store: sessions.NewCookieStore([]byte("flQ6QzM/c3Jtdl9ycDx6OXRIfFgK")),
}
app := photo.PhotoBlog{ app := photo.PhotoBlog{
AuthCookie: &admin, admin.AuthCookie{Templates: tpl,
Templates: tpl, Store: sessions.NewCookieStore([]byte("flQ6QzM/c3Jtdl9ycDx6OXRIfFgK")),
DataDir: data, DataDir: data,
PasswordSecret: "d2xnNSwoREQhfSxBVDQ0bF0yb2AK",
},
} }
fileServer := http.FileServer(http.Dir(data.Name())) fileServer := http.FileServer(http.Dir(data.Name()))
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
fmt.Println(req.RequestURI)
if strings.HasPrefix(req.RequestURI, "/data") { if strings.HasPrefix(req.RequestURI, "/data") {
http.StripPrefix("/data/", fileServer).ServeHTTP(res, req) http.StripPrefix("/data/", fileServer).ServeHTTP(res, req)
} else { } else {

View File

@ -2,7 +2,6 @@ package photo
import ( import (
"fmt" "fmt"
"html/template"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -25,8 +24,8 @@ var (
type AppData struct { type AppData struct {
Username string Username string
Photos []string Photos []string
errors []error Err error
messages []string Message string
} }
type TimedFile struct { type TimedFile struct {
@ -47,9 +46,7 @@ func (m timeMap) Swap(i, j int) {
} }
type PhotoBlog struct { type PhotoBlog struct {
*admin.AuthCookie admin.AuthCookie
Templates *template.Template
DataDir *os.File
} }
func RedirectHome(res http.ResponseWriter, req *http.Request) { func RedirectHome(res http.ResponseWriter, req *http.Request) {
@ -99,14 +96,16 @@ func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) {
RedirectHome(res, req) RedirectHome(res, req)
return return
} }
data := make(map[string]string) data := AppData{
Username: app.Username(res, req),
}
switch req.Method { switch req.Method {
case "POST": case "POST":
message, err := app.NewPhoto(res, req) message, err := app.NewPhoto(res, req)
if err != nil { if err != nil {
data["error"] = err.Error() data.Err = err
} }
data["message"] = message data.Message = message
fallthrough fallthrough
case "GET": case "GET":
app.Templates.ExecuteTemplate(res, "upload.html", data) app.Templates.ExecuteTemplate(res, "upload.html", data)

View File

@ -1,2 +1,87 @@
</main>
</body> </body>
<style>
.error {
color: red;
}
ul.photos {
list-style-type: none;
}
img {
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px;
width: 400px;
}
main {
width: 800px;
margin: auto;
}
header {
margin: 0;
padding: 0;
}
body {
margin:0;
font-family: Verdana,sans-serif;
}
header ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
header li {
float: left;
}
header p {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
margin: 0
}
header a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
div.form-container {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}
input[type=submit] {
width: 50%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #45a049;
}
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
a.mainlink {
color: #4CAF50;
}
.form-container a {
color: #4CAF50;
padding-left: 100px;
}
</style>
</html> </html>

View File

@ -2,3 +2,16 @@
<html> <html>
<head><title>Photo blog</title></head> <head><title>Photo blog</title></head>
<body> <body>
<header>
<ul>
<li><p>Home</p></li>
{{ if .Username }}
<li><p>Hello {{ .Username }}</p></li>
<li style="float: right"><a href="/logout">Logout</a></li>
{{ else }}
<li><p>You are not logged in</p></li>
<li style="float: right"><a href="/login">Login</a></li>
{{ end }}
</ul>
</header>
<main>

View File

@ -1,24 +1,13 @@
{{ template "header.html" }} {{ template "header.html" . }}
<header>
{{ if .Username }}
<p>Hello {{ .Username }}</p>
<a href="/logout">Logout</a>
{{ else }}
<p>You are not logged in<p>
<a href="/login">Login</a>
{{ end }}
</header>
<div>
</div>
<h1>Photo blog</h1> <h1>Photo blog</h1>
{{ if .Username }} {{ if .Username }}
<a href="upload">Add photo</a> <a class="mainlink" href="upload">Add photo</a>
{{ end }}
<ul>
{{ range .Photos }}
<li><img src="{{ . }}" style="width:320px;height:200px;"></li>
{{ else }}
<li>No photos</li>
{{ end }} {{ end }}
<ul class="photos">
{{ range .Photos }}
<li><a href="{{ . }}"><img src="{{ . }}"></a></li>
{{ else }}
<li>No photos</li>
{{ end }}
</ul> </ul>
{{ template "footer.html" }} {{ template "footer.html" }}

View File

@ -1,16 +1,19 @@
{{ template "header.html" }} {{ template "header.html" . }}
<h1>Authentification</h1> <h1>Authentification</h1>
<form action="/login" method="POST"> <div class="form-container">
<label> <form action="/login" method="POST">
Username: {{ if .username }}<b>{{ .username }}</b>{{ end }} <label>
<input type="text" name="username"> Username: {{ if .username }}<b class="error">{{ .username }}</b>{{ end }}
</label> <input type="text" name="username" placeholder="login...">
<br> </label>
<label> <br>
Password: {{ if .password }}<b>{{ .password }}</b>{{ end }} <label>
<input type="password" name="password"> Password: {{ if .password }}<b class="error">{{ .password }}</b>{{ end }}
</label> <input type="password" name="password" placeholder="password...">
<br> </label>
<input type="submit"> <br>
</form> <input type="submit">
<a href="/">Back</a>
</form>
</div>
{{ template "footer.html" }} {{ template "footer.html" }}

View File

@ -1,14 +1,16 @@
{{ template "header.html" }} {{ template "header.html" . }}
<h1>Send photo</h1> <h1>Send photo</h1>
<form action="/upload" method="POST" enctype="multipart/form-data"> <div class="form-container">
{{ if .message }}<p class="message">{{ .message }}</p>{{ end }} <form action="/upload" method="POST" enctype="multipart/form-data">
{{ if .error }}<p class="errormsg">{{ .error }}</p>{{ end }} {{ if .Message }}<p class="message">{{ .Message }}</p>{{ end }}
<label> {{ if .Err }}<p class="error">{{ .Err }}</p>{{ end }}
Select photo: {{ if .file }}<b>{{ .file }}</b>{{ end }} <label>
<input type="file" name="file"> Select photo:
</label> <input type="file" name="file">
<br> </label>
<input type="submit"> <br>
</form> <input type="submit" value="Send">
<a href="/">Back</a> <a href="/">Back</a>
</form>
</div>
{{ template "footer.html" }} {{ template "footer.html" }}