]> Cypherpunks.ru repositories - gocheese.git/blob - passwd.go
-auth-required option and optional :ro per-user attribute
[gocheese.git] / passwd.go
1 // GoCheese -- Python private package repository and caching proxy
2 // Copyright (C) 2019-2024 Sergey Matveev <stargrave@stargrave.org>
3 //               2019-2024 Elena Balakhonova <balakhonova_e@riseup.net>
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, version 3 of the License.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 package main
18
19 import (
20         "bufio"
21         "context"
22         "errors"
23         "log"
24         "net/http"
25         "os"
26         "strings"
27         "sync"
28 )
29
30 var (
31         Passwords  map[string]*User = make(map[string]*User)
32         PasswordsM sync.RWMutex
33 )
34
35 type CtxUserKeyType struct{}
36
37 var CtxUserKey CtxUserKeyType
38
39 type Auther interface {
40         Auth(password string) bool
41 }
42
43 type User struct {
44         name   string
45         ro     bool
46         auther Auther
47 }
48
49 func strToAuther(verifier string) (string, Auther, error) {
50         st := strings.SplitN(verifier, "$", 3)
51         if len(st) != 3 || st[0] != "" {
52                 return "", nil, errors.New("invalid verifier structure")
53         }
54         algorithm := st[1]
55         var auther Auther
56         var err error
57         switch algorithm {
58         case "argon2i":
59                 auther, err = parseArgon2i(st[2])
60         case "sha256":
61                 auther, err = parseSHA256(st[2])
62         default:
63                 err = errors.New("unknown hashing algorithm")
64         }
65         return algorithm, auther, err
66 }
67
68 func passwdReader(fd *os.File) bool {
69         isGood := true
70         scanner := bufio.NewScanner(fd)
71         for scanner.Scan() {
72                 t := scanner.Text()
73                 if len(t) == 0 {
74                         continue
75                 }
76                 splitted := strings.Split(t, ":")
77                 if len(splitted) < 2 {
78                         log.Println("wrong login:password[:ro] format:", t)
79                         isGood = false
80                         continue
81                 }
82                 login := splitted[0]
83                 passwd := splitted[1]
84                 if passwd == "" {
85                         log.Println("deleting login:", login)
86                         PasswordsM.Lock()
87                         delete(Passwords, login)
88                         PasswordsM.Unlock()
89                         continue
90                 }
91                 _, auther, err := strToAuther(passwd)
92                 if err != nil {
93                         log.Println("login:", login, "invalid password:", err)
94                         isGood = false
95                         continue
96                 }
97                 var ro bool
98                 if len(splitted) > 2 {
99                         switch splitted[2] {
100                         case "ro":
101                                 ro = true
102                         default:
103                                 log.Println("wrong format of optional field:", t)
104                                 isGood = false
105                                 continue
106                         }
107                 }
108                 log.Println("adding password for:", login)
109                 PasswordsM.Lock()
110                 Passwords[login] = &User{name: login, ro: ro, auther: auther}
111                 PasswordsM.Unlock()
112         }
113         return isGood
114 }
115
116 func passwdLister(fd *os.File) {
117         PasswordsM.RLock()
118         logins := make([]string, 0, len(Passwords))
119         for login := range Passwords {
120                 logins = append(logins, login)
121         }
122         PasswordsM.RUnlock()
123         for _, login := range logins {
124                 fd.WriteString(login + "\n")
125         }
126 }
127
128 func checkAuth(handler http.HandlerFunc) http.HandlerFunc {
129         return func(w http.ResponseWriter, r *http.Request) {
130                 username, password, gotAuth := r.BasicAuth()
131                 var user *User
132                 if gotAuth {
133                         PasswordsM.RLock()
134                         user = Passwords[username]
135                         PasswordsM.RUnlock()
136                 }
137                 var passwordValid bool
138                 if gotAuth && user != nil {
139                         passwordValid = user.auther.Auth(password)
140                 }
141                 if (gotAuth && user == nil) ||
142                         (user != nil && !passwordValid) ||
143                         (*AuthRequired && !gotAuth) {
144                         log.Println(r.RemoteAddr, "unauthenticated", username)
145                         http.Error(w, "unauthenticated", http.StatusUnauthorized)
146                         return
147                 }
148                 handler(w, r.WithContext(context.WithValue(r.Context(), CtxUserKey, user)))
149         }
150 }