Photoblog: Refactoring
This commit is contained in:
parent
8ec59e4826
commit
b8f03b87c9
@ -15,6 +15,14 @@ import (
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
const (
|
||||
USER = "username"
|
||||
PWD = "password"
|
||||
SESS_USER = "username"
|
||||
SESSION = "photoblog.session"
|
||||
)
|
||||
|
||||
// Application
|
||||
type AuthCookie struct {
|
||||
Templates *template.Template
|
||||
Store *sessions.CookieStore
|
||||
@ -22,37 +30,23 @@ type AuthCookie struct {
|
||||
PasswordSecret string
|
||||
}
|
||||
|
||||
func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) {
|
||||
formErr := make(map[string]error)
|
||||
switch req.Method {
|
||||
case "POST":
|
||||
username := req.FormValue("username")
|
||||
formErr["username"] = app.VerifyUsername(username)
|
||||
formErr["password"] = app.VerifyPassword(username, req.FormValue("password"), res, req)
|
||||
if formErr["username"] == nil && formErr["password"] == nil {
|
||||
app.SaveUsername(username, res, req)
|
||||
RedirectHome(res, req)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case "GET":
|
||||
app.Templates.ExecuteTemplate(res, "login.html", formErr)
|
||||
}
|
||||
}
|
||||
func (app *AuthCookie) LogoutPage(res http.ResponseWriter, req *http.Request) {
|
||||
app.SaveUsername("", res, req)
|
||||
RedirectHome(res, req)
|
||||
}
|
||||
// Verify Username
|
||||
func (app *AuthCookie) VerifyUsername(username string) error {
|
||||
if username == "" {
|
||||
return errors.New("Empty username")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify password for user, check with file
|
||||
func (app *AuthCookie) VerifyPassword(username, password string, res http.ResponseWriter, req *http.Request) error {
|
||||
if password == "" {
|
||||
return errors.New("Empty password")
|
||||
}
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
// open password file
|
||||
passfile, err := os.Open(filepath.Join(app.DataDir.Name(), username, ".password"))
|
||||
if err != nil {
|
||||
log.Println("Cannot open password file", err)
|
||||
@ -60,14 +54,13 @@ func (app *AuthCookie) VerifyPassword(username, password string, res http.Respon
|
||||
}
|
||||
defer passfile.Close()
|
||||
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
// read password file
|
||||
expected, err := ioutil.ReadAll(passfile)
|
||||
if err != nil {
|
||||
log.Println("Cannot read password file", err)
|
||||
return errors.New("Authentification failed")
|
||||
}
|
||||
// hash password and compare
|
||||
expectedStr := string(expected)
|
||||
var expectedMAC []byte
|
||||
fmt.Sscanf(expectedStr, "%x", &expectedMAC)
|
||||
@ -78,29 +71,67 @@ func (app *AuthCookie) VerifyPassword(username, password string, res http.Respon
|
||||
log.Printf("Unmatched password for %s: %x - %x\n", username, passwordMAC, expectedMAC)
|
||||
return errors.New("Authentification failed")
|
||||
}
|
||||
log.Printf("Authentification successful")
|
||||
log.Printf("Authentification successful (" + username + ")")
|
||||
return nil
|
||||
}
|
||||
func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session {
|
||||
session, _ := app.Store.Get(req, "session")
|
||||
return session
|
||||
}
|
||||
|
||||
// Save username in session
|
||||
func (app *AuthCookie) SaveUsername(username string, res http.ResponseWriter, req *http.Request) {
|
||||
session := app.CurrentSession(res, req)
|
||||
session.Values["username"] = username
|
||||
session.Values[SESS_USER] = username
|
||||
session.Save(req, res)
|
||||
}
|
||||
|
||||
// Test if a user is logged in
|
||||
func (app *AuthCookie) IsLoggedIn(res http.ResponseWriter, req *http.Request) bool {
|
||||
session := app.CurrentSession(res, req)
|
||||
return session != nil && session.Values["username"] != ""
|
||||
return session != nil && session.Values[SESS_USER] != ""
|
||||
}
|
||||
|
||||
// Current username (from session cookie)
|
||||
func (app *AuthCookie) Username(res http.ResponseWriter, req *http.Request) string {
|
||||
session := app.CurrentSession(res, req)
|
||||
if session == nil {
|
||||
return ""
|
||||
}
|
||||
return session.Values["username"].(string)
|
||||
val := session.Values[SESS_USER]
|
||||
if val == nil {
|
||||
return ""
|
||||
}
|
||||
func RedirectHome(res http.ResponseWriter, req *http.Request) {
|
||||
return val.(string)
|
||||
}
|
||||
|
||||
// Current session
|
||||
func (app *AuthCookie) CurrentSession(res http.ResponseWriter, req *http.Request) *sessions.Session {
|
||||
session, _ := app.Store.Get(req, SESSION)
|
||||
return session
|
||||
}
|
||||
|
||||
// Redirect to home page
|
||||
func (app *AuthCookie) RedirectHome(res http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(res, req, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// ROUTES //
|
||||
|
||||
// login: form
|
||||
func (app *AuthCookie) LoginPage(res http.ResponseWriter, req *http.Request) {
|
||||
formErr := make(map[string]error)
|
||||
if req.Method == http.MethodPost {
|
||||
username := req.FormValue(USER)
|
||||
formErr[USER] = app.VerifyUsername(username)
|
||||
formErr[PWD] = app.VerifyPassword(username, req.FormValue(PWD), res, req)
|
||||
if formErr[USER] == nil && formErr[PWD] == nil {
|
||||
app.SaveUsername(username, res, req)
|
||||
app.RedirectHome(res, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
app.Templates.ExecuteTemplate(res, "login.html", formErr)
|
||||
}
|
||||
|
||||
// logout: delete session and redirect to home
|
||||
func (app *AuthCookie) LogoutPage(res http.ResponseWriter, req *http.Request) {
|
||||
app.SaveUsername("", res, req)
|
||||
app.RedirectHome(res, req)
|
||||
}
|
||||
|
@ -28,19 +28,19 @@ func main() {
|
||||
}
|
||||
defer data.Close()
|
||||
|
||||
// FIXME config file
|
||||
app := photo.PhotoBlog{
|
||||
admin.AuthCookie{Templates: tpl,
|
||||
admin.AuthCookie{
|
||||
Templates: tpl,
|
||||
Store: sessions.NewCookieStore([]byte("flQ6QzM/c3Jtdl9ycDx6OXRIfFgK")),
|
||||
DataDir: data,
|
||||
PasswordSecret: "d2xnNSwoREQhfSxBVDQ0bF0yb2AK",
|
||||
},
|
||||
}
|
||||
|
||||
fileServer := http.FileServer(http.Dir(data.Name()))
|
||||
|
||||
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
||||
if strings.HasPrefix(req.RequestURI, "/data") {
|
||||
http.StripPrefix("/data/", fileServer).ServeHTTP(res, req)
|
||||
http.StripPrefix("/data/", http.FileServer(http.Dir(data.Name()))).ServeHTTP(res, req)
|
||||
} else {
|
||||
app.HomePage(res, req)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package photo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -19,21 +20,58 @@ const (
|
||||
|
||||
var (
|
||||
PHOTOEXT = [3]string{".jpg", ".jpeg", ".png"}
|
||||
PHOTOMIME = [2]string{"image/png", "image/jpeg"}
|
||||
)
|
||||
|
||||
type AppData struct {
|
||||
// Application
|
||||
type PhotoBlog struct {
|
||||
admin.AuthCookie
|
||||
}
|
||||
|
||||
// Generic model
|
||||
type PhotoModel struct {
|
||||
Username string
|
||||
Photos []string
|
||||
Err error
|
||||
Message string
|
||||
}
|
||||
|
||||
// Constructor PhotoModel
|
||||
func NewPhotoModel(username string) *PhotoModel {
|
||||
return &PhotoModel{
|
||||
Username: username,
|
||||
}
|
||||
}
|
||||
|
||||
// Home page model
|
||||
type PhotoHomeModel struct {
|
||||
PhotoModel
|
||||
Photos []string
|
||||
}
|
||||
|
||||
// Constructor PhotoHomeModel
|
||||
func NewPhotoHomeModel(username string, photos []string) *PhotoHomeModel {
|
||||
return &PhotoHomeModel{
|
||||
PhotoModel: *NewPhotoModel(username),
|
||||
Photos: photos,
|
||||
}
|
||||
}
|
||||
|
||||
// File and date of last modification
|
||||
type TimedFile struct {
|
||||
Mod time.Time
|
||||
Path string
|
||||
}
|
||||
|
||||
type timeMap []TimedFile
|
||||
// Constructor TimedFile
|
||||
func NewTimedFile(mod time.Time, path string) *TimedFile {
|
||||
return &TimedFile{
|
||||
mod,
|
||||
path,
|
||||
}
|
||||
}
|
||||
|
||||
// TimedFile sort
|
||||
type timeMap []*TimedFile
|
||||
|
||||
func (m timeMap) Len() int {
|
||||
return len(m)
|
||||
@ -45,14 +83,7 @@ func (m timeMap) Swap(i, j int) {
|
||||
m[i], m[j] = m[j], m[i]
|
||||
}
|
||||
|
||||
type PhotoBlog struct {
|
||||
admin.AuthCookie
|
||||
}
|
||||
|
||||
func RedirectHome(res http.ResponseWriter, req *http.Request) {
|
||||
// FIXME duplicate admin
|
||||
http.Redirect(res, req, "/", http.StatusSeeOther)
|
||||
}
|
||||
// test if file is a photo (by extension)
|
||||
func IsPhoto(path string) bool {
|
||||
for _, ext := range PHOTOEXT {
|
||||
if strings.HasSuffix(path, ext) {
|
||||
@ -62,6 +93,17 @@ func IsPhoto(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// test if mime type is a photo
|
||||
func IsPhotoMime(mime string) bool {
|
||||
for _, accepted := range PHOTOMIME {
|
||||
if mime == accepted {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// get Latest files
|
||||
func Latest(photos timeMap) []string {
|
||||
latest := make([]string, 0, DISPLAY_SIZE)
|
||||
sort.Sort(photos)
|
||||
@ -74,60 +116,57 @@ func Latest(photos timeMap) []string {
|
||||
return latest
|
||||
}
|
||||
|
||||
func (app *PhotoBlog) HomePage(res http.ResponseWriter, req *http.Request) {
|
||||
data := AppData{
|
||||
Username: app.Username(res, req),
|
||||
}
|
||||
photos := make([]TimedFile, 0)
|
||||
filepath.Walk(app.DataDir.Name(), func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && IsPhoto(path) {
|
||||
photos = append(photos, TimedFile{
|
||||
info.ModTime(),
|
||||
path,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
data.Photos = Latest(photos)
|
||||
app.Templates.ExecuteTemplate(res, "home.html", data)
|
||||
}
|
||||
func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) {
|
||||
if !app.IsLoggedIn(res, req) {
|
||||
RedirectHome(res, req)
|
||||
return
|
||||
}
|
||||
data := AppData{
|
||||
Username: app.Username(res, req),
|
||||
}
|
||||
switch req.Method {
|
||||
case "POST":
|
||||
message, err := app.NewPhoto(res, req)
|
||||
if err != nil {
|
||||
data.Err = err
|
||||
}
|
||||
data.Message = message
|
||||
fallthrough
|
||||
case "GET":
|
||||
app.Templates.ExecuteTemplate(res, "upload.html", data)
|
||||
}
|
||||
}
|
||||
func (app *PhotoBlog) NewPhoto(res http.ResponseWriter, req *http.Request) (string, error) {
|
||||
// Add new uploaded photo
|
||||
func (app *PhotoBlog) AddPhoto(res http.ResponseWriter, req *http.Request) (string, error) {
|
||||
file, header, err := req.FormFile("file")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// FIXME check type
|
||||
tmpFile, err := os.Create(filepath.Join(app.DataDir.Name(), app.Username(res, req), header.Filename))
|
||||
mime := header.Header.Get("Content-Type")
|
||||
if !IsPhotoMime(mime) {
|
||||
return "", errors.New("This file type is not accepted (" + mime + ")")
|
||||
}
|
||||
|
||||
target, err := os.Create(filepath.Join(app.DataDir.Name(), app.Username(res, req), header.Filename))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer tmpFile.Close()
|
||||
defer target.Close()
|
||||
|
||||
sz, err := io.Copy(tmpFile, file)
|
||||
sz, err := io.Copy(target, file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("File uploaded (%d bytes)", sz), nil
|
||||
}
|
||||
|
||||
// ROUTES //
|
||||
|
||||
// Home: list latest photos
|
||||
func (app *PhotoBlog) HomePage(res http.ResponseWriter, req *http.Request) {
|
||||
photos := make([]*TimedFile, 0)
|
||||
filepath.Walk(app.DataDir.Name(), func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() && IsPhoto(path) {
|
||||
photos = append(photos, NewTimedFile(info.ModTime(), path))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
app.Templates.ExecuteTemplate(res, "home.html", NewPhotoHomeModel(app.Username(res, req), Latest(photos)))
|
||||
}
|
||||
|
||||
// Upload: form upload photo
|
||||
func (app *PhotoBlog) UploadPage(res http.ResponseWriter, req *http.Request) {
|
||||
if !app.IsLoggedIn(res, req) {
|
||||
app.RedirectHome(res, req)
|
||||
return
|
||||
}
|
||||
data := NewPhotoModel(app.Username(res, req))
|
||||
if req.Method == http.MethodPost {
|
||||
message, err := app.AddPhoto(res, req)
|
||||
data.Err = err
|
||||
data.Message = message
|
||||
}
|
||||
app.Templates.ExecuteTemplate(res, "upload.html", data)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
<body>
|
||||
<header>
|
||||
<ul>
|
||||
<li><p>Home</p></li>
|
||||
<li><a href="/">Home</a></li>
|
||||
{{ if .Username }}
|
||||
<li><p>Hello {{ .Username }}</p></li>
|
||||
<li style="float: right"><a href="/logout">Logout</a></li>
|
||||
|
@ -12,7 +12,7 @@
|
||||
<input type="password" name="password" placeholder="password...">
|
||||
</label>
|
||||
<br>
|
||||
<input type="submit">
|
||||
<input type="submit" value="Login">
|
||||
<a href="/">Back</a>
|
||||
</form>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user