mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2026-10-20 16:54:32 +00:00
210 lines
5.1 KiB
Go
210 lines
5.1 KiB
Go
package share
|
|
|
|
import (
|
|
_ "embed"
|
|
|
|
"bytes"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"html/template"
|
|
"strings"
|
|
|
|
. "github.com/mickael-kerjean/filestash/server/pkg/config"
|
|
. "github.com/mickael-kerjean/filestash/server/pkg/core"
|
|
. "github.com/mickael-kerjean/filestash/server/pkg/utils"
|
|
|
|
"github.com/mickael-kerjean/filestash/server/pkg/env"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gopkg.in/gomail.v2"
|
|
)
|
|
|
|
//go:embed static/verification.html
|
|
var tmplEmailVerification string
|
|
|
|
func ShareList(backend string, path string) ([]Share, error) {
|
|
rows, err := DB.Query(`
|
|
SELECT id, related_path, params
|
|
FROM Share
|
|
WHERE related_backend = ? AND related_path LIKE ? || '%'
|
|
`, backend, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
shares := []Share{}
|
|
for rows.Next() {
|
|
var (
|
|
share Share
|
|
tmp []byte
|
|
)
|
|
rows.Scan(&share.Id, &share.Path, &tmp)
|
|
json.Unmarshal(tmp, &share)
|
|
shares = append(shares, share)
|
|
}
|
|
rows.Close()
|
|
return shares, nil
|
|
}
|
|
|
|
func ShareAll() ([]Share, error) {
|
|
rows, err := DB.Query(`
|
|
SELECT id, related_path, params FROM Share
|
|
`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
shares := []Share{}
|
|
for rows.Next() {
|
|
var share Share
|
|
var tmp []byte
|
|
rows.Scan(&share.Id, &share.Path, &tmp)
|
|
json.Unmarshal(tmp, &share)
|
|
shares = append(shares, share)
|
|
}
|
|
rows.Close()
|
|
return shares, nil
|
|
}
|
|
|
|
func ShareGet(id string) (Share, error) {
|
|
var (
|
|
share Share
|
|
tmp []byte
|
|
)
|
|
row := DB.QueryRow("SELECT id, related_backend, related_path, auth, params FROM share WHERE id = ?", id)
|
|
if err := row.Scan(&share.Id, &share.Backend, &share.Path, &share.Auth, &tmp); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return share, ErrNotFound
|
|
}
|
|
return share, err
|
|
}
|
|
json.Unmarshal(tmp, &share)
|
|
return share, nil
|
|
}
|
|
|
|
func ShareUpsert(p *Share) error {
|
|
if p.Password != nil {
|
|
if *p.Password == PASSWORD_DUMMY {
|
|
s, err := ShareGet(p.Id)
|
|
if err != nil {
|
|
return ErrNotValid
|
|
}
|
|
p.Password = s.Password
|
|
} else {
|
|
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(*p.Password), bcrypt.DefaultCost)
|
|
p.Password = NewString(string(hashedPassword))
|
|
}
|
|
}
|
|
|
|
j, _ := json.Marshal(&struct {
|
|
Password *string `json:"password,omitempty"`
|
|
Users *string `json:"users,omitempty"`
|
|
Expire *int64 `json:"expire,omitempty"`
|
|
CanRead bool `json:"can_read"`
|
|
CanWrite bool `json:"can_write"`
|
|
CanUpload bool `json:"can_upload"`
|
|
}{
|
|
Password: p.Password,
|
|
Users: p.Users,
|
|
Expire: p.Expire,
|
|
CanRead: p.CanRead,
|
|
CanWrite: p.CanWrite,
|
|
CanUpload: p.CanUpload,
|
|
})
|
|
_, err := DB.Exec(`
|
|
INSERT INTO Share(id, related_backend, related_path, params, auth)
|
|
VALUES($1, $2, $3, $4, $5)
|
|
ON CONFLICT(id) DO UPDATE SET related_backend = $2, related_path = $3, params = $4
|
|
`, p.Id, p.Backend, p.Path, j, p.Auth)
|
|
return err
|
|
}
|
|
|
|
func ShareDelete(id string) error {
|
|
_, err := DB.Exec("DELETE FROM Share WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
func ShareVerify(share Share, proof Proof) (Proof, error) {
|
|
p := proof
|
|
if proof.Key == "password" {
|
|
if share.Password == nil {
|
|
return p, NewError("No password required", 400)
|
|
}
|
|
v, ok := ShareVerifyPassword(*share.Password, proof.Value)
|
|
if ok == false {
|
|
return p, ErrInvalidPassword
|
|
}
|
|
p.Value = v
|
|
}
|
|
|
|
if proof.Key == "email" {
|
|
// find out if user is authorized
|
|
if share.Users == nil {
|
|
return p, NewError("Authentication not required", 400)
|
|
}
|
|
v, ok := ShareVerifyEmail(*share.Users, proof.Value)
|
|
if ok == false {
|
|
return p, ErrNotAuthorized
|
|
}
|
|
user := v
|
|
code := RandomString(6)
|
|
// prepare the verification code
|
|
if _, err := DB.Exec(
|
|
"INSERT INTO Verification(key, code) VALUES(?, ?)",
|
|
"email::"+v, code,
|
|
); err != nil {
|
|
return p, err
|
|
}
|
|
// Prepare message
|
|
var b bytes.Buffer
|
|
t := template.New("email")
|
|
t.Parse(tmplEmailVerification)
|
|
t.Execute(&b, struct {
|
|
Code string
|
|
Username string
|
|
WhiteLabel bool
|
|
}{
|
|
code,
|
|
user + "[" + Hash(user+env.SECRET_KEY_DERIVATE_FOR_HASH, 10) + "]",
|
|
env.IsWhiteLabel(),
|
|
})
|
|
p.Key = "code"
|
|
p.Message = NewString("We've sent you a message with a verification code")
|
|
// Send email
|
|
m := gomail.NewMessage()
|
|
m.SetHeader("From", Config.Get("email.from").String())
|
|
m.SetHeader("To", proof.Value)
|
|
m.SetHeader("Subject", "Your verification code")
|
|
m.SetBody("text/html", b.String())
|
|
d := gomail.NewDialer(
|
|
Config.Get("email.server").String(),
|
|
Config.Get("email.port").Int(),
|
|
Config.Get("email.username").String(),
|
|
Config.Get("email.password").String(),
|
|
)
|
|
if err := d.DialAndSend(m); err != nil {
|
|
Log.Warning("pkg::share::model action=send error=%q", err)
|
|
Log.Debug("pkg::share::model action=send code=%q", code)
|
|
return p, NewError("Couldn't send email", 500)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
if proof.Key == "code" {
|
|
var key string
|
|
if err := DB.QueryRow(
|
|
"SELECT key FROM Verification WHERE code = ? AND expire > datetime('now')",
|
|
proof.Value,
|
|
).Scan(&key); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
p.Key = "email"
|
|
return p, NewError("Not found", 404)
|
|
}
|
|
return p, err
|
|
}
|
|
DB.Exec("DELETE FROM Verification WHERE code = ?", proof.Value)
|
|
p.Key = "email"
|
|
p.Value = strings.TrimPrefix(key, "email::")
|
|
}
|
|
|
|
return p, nil
|
|
}
|