Skip to content

Commit b91ec31

Browse files
authored
Merge pull request #4 from lpimem/develop
bugfix
2 parents 6d3c106 + 9bdc5d9 commit b91ec31

39 files changed

+649
-98
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ db/*.db
55
.idea
66
static/*.js
77
static/*.map
8+
*.env
9+
.DS_Store

README.md

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
HLC Server
44

5-
65
## Benchmarks
76

87
### 04/20/2017
@@ -14,4 +13,19 @@ BenchmarkGetPagenote/10-8 100000 198909 ns/op 16058 B/op 562 all
1413
BenchmarkGetPagenote/100-8 20000 776848 ns/op 72858 B/op 3540 allocs/op
1514
BenchmarkGetPagenote/1000-8 2000 6432966 ns/op 690328 B/op 33258 allocs/op
1615
BenchmarkGetPagenoteP-8 50000 293017 ns/op 73156 B/op 3543 allocs/op
16+
```
17+
18+
## Notes
19+
20+
Set environmnet variables before deploying/testing.
21+
22+
- `HLC_SESSION_SECRET`
23+
- `HLC_SESSION_KEY_USER`
24+
- `HLC_SESSION_KEY_SID`
25+
- `GOOGLE_OAUTH2_CLIENT_ID`
26+
27+
Generate a random string:
28+
29+
```bash
30+
env LC_CTYPE=C tr -dc "a-zA-Z0-9-_\$\?" < /dev/urandom | fold -w 64 | head -n 1
1731
```

auth/auth.go

+18-12
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ import (
1616
const admin string = "admin"
1717
const adminUserID storage.UserID = 1
1818

19+
20+
1921
// Authenticate implements the interceptor interface.
2022
// It adds a flag to the request context to indicate if the
2123
// request is authenticated. If authenticated, it also checks
2224
// if the request is trying to access an admin URI and returns
2325
// error if the request is not from the admin user.
24-
func Authenticate(req *http.Request) (*http.Request, error) {
26+
func Authenticate(req *http.Request, respWriter http.ResponseWriter) (*http.Request, bool, error) {
2527
var (
2628
uid storage.UserID
2729
sid string
@@ -35,21 +37,25 @@ func Authenticate(req *http.Request) (*http.Request, error) {
3537
if err != nil {
3638
log.Info("cannot extract uid/sid:", sid, uid, err)
3739
ctx = context.WithValue(ctx, REASON, err.Error())
38-
req = req.WithContext(ctx)
39-
return req, authorize(ctx, req)
40-
}
41-
if err = VerifySession(sid, uid, nil); err != nil {
40+
} else if err = VerifySession(sid, uid, nil); err != nil {
4241
log.Info("invalid session", sid, uid, err)
4342
ctx = context.WithValue(ctx, REASON, err.Error())
44-
req = req.WithContext(ctx)
45-
return req, authorize(ctx, req)
43+
} else {
44+
log.Debug("User ", uid, " is authenticated")
45+
ctx = context.WithValue(ctx, USER_ID, uid)
46+
ctx = context.WithValue(ctx, SESSION_ID, sid)
47+
ctx = context.WithValue(ctx, AUTHENTICATED, true)
4648
}
47-
ctx = context.WithValue(ctx, USER_ID, uid)
48-
ctx = context.WithValue(ctx, SESSION_ID, sid)
49-
ctx = context.WithValue(ctx, AUTHENTICATED, true)
5049
req = req.WithContext(ctx)
51-
log.Info("request from", uid, "is authorized.")
52-
return req, authorize(ctx, req)
50+
err = authorize(ctx, req)
51+
if err != nil {
52+
log.Warn("User ", uid, " is not authorized to access ", req.URL.Path)
53+
ctx = context.WithValue(ctx, REASON, err.Error())
54+
req = req.WithContext(ctx)
55+
conf.RedirectToLogin(conf.EncodePath(req.URL), respWriter, req)
56+
}
57+
handledIfError := true
58+
return req, handledIfError, err
5359
}
5460

5561
// IsSessionTimeout returns if duration since lastAccess exceeds the max session lifetime

auth/auth_test.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ func TestAuthenticate(t *testing.T) {
5656
&authCase{"sid missing", "", uint32(10), false},
5757
}
5858

59+
var w *httptest.ResponseRecorder
60+
5961
for _, byCookie := range []bool{true, false} {
6062
for _, tc := range testCases {
6163
var tcname string
@@ -73,14 +75,16 @@ func TestAuthenticate(t *testing.T) {
7375
if byCookie {
7476
req, err = setByCookie(req, tc.UID, tc.Sid)
7577
if err != nil {
78+
t.Error(conf.SessionKeySID())
7679
t.Error(err)
7780
t.Fail()
7881
return
7982
}
8083
} else {
8184
req, err = setByHeader(req, tc.UID, tc.Sid)
8285
}
83-
req, err = Authenticate(req)
86+
w = httptest.NewRecorder()
87+
req, _, err = Authenticate(req, w)
8488
if IsAuthenticated(req) != tc.Suc {
8589
log.Debug(req.Context().Value(AUTHENTICATED))
8690
log.Debug(req.Context().Value(USER_ID))
@@ -134,7 +138,9 @@ func TestAuthorizeAdmin(t *testing.T) {
134138
t.Fail()
135139
return
136140
}
137-
req, err = Authenticate(req)
141+
142+
w := httptest.NewRecorder()
143+
req, _, err = Authenticate(req, w)
138144
pass := IsAuthenticated(req) && err == nil
139145
if pass != tc.Suc {
140146
if err != nil {

build-macos.sh

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
DIR=$(dirname `realpath -P $0`)
2+
cd $DIR
3+
CGO_ENABLED=1 go build --ldflags -s "$@" -v -o build/hlcsrv
4+
ret=$!
5+
if [[ ${ret} -ne 0 ]]; then
6+
echo "If go-sqlite3 is complaining, try the following commands.
7+
brew install FiloSottile/musl-cross/musl-cross
8+
brew install mingw-w64
9+
If you are cross compiling from macos to linux, try this:
10+
GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc ./build-macos.sh
11+
Reference:
12+
* https://github.com/mattn/go-sqlite3/issues/372#issuecomment-396863368
13+
* https://github.com/mattn/go-sqlite3/issues/372#issuecomment-398001083"
14+
exit ${ret}
15+
fi
16+
rsync -a db/*.sql build/db/
17+
rsync -a view build/
18+
rsync -a static build/
19+
rsync -a scripts/*.sh build/
20+
exit $ret

build.sh

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
cp db/*.sql build/db/
1+
DIR=$(dirname `readlink -f $0`)
2+
cd $DIR
3+
24
go build --ldflags -s -o build/hlcsrv
3-
rsync -a --progress view build/
5+
ret=$!
6+
rsync -a db/*.sql build/db/
7+
rsync -a view build/
8+
rsync -a static build/
9+
rsync -a scripts/*.sh build/
10+
exit $ret

clean.sh

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DIR=$(dirname `readlink -f "$0"`)
2+
3+
if [[ -d $DIR/build/ ]] ; then
4+
rm -rf $DIR/build/*
5+
fi

conf/config.go

+50-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
package conf
22

33
import "os"
4+
import "net/http"
5+
import "net/url"
6+
import "github.com/go-playground/log"
47

58
var debugFlag = false
69

10+
const (
11+
_HLC_NEXT = "hlc.next"
12+
)
13+
714
// IsDebug returns true should the app run in debugging mode.
815
func IsDebug() bool {
16+
if os.Getenv("HLC_DEBUG") == "1"{
17+
return true
18+
}
919
return debugFlag
1020
}
1121

@@ -22,7 +32,7 @@ func SetDebug(option bool) {
2232
* ```
2333
*/
2434
func SessionSecret() string {
25-
return "8M3W5A0CGYFx_bzjMzAFqLZ8esI3F0_CveBbgDZLd0hc2ManB3il2Cw9IPcY7Fr1"
35+
return os.Getenv("HLC_SESSION_SECRET")
2636
}
2737

2838
// Page should be a request parameter, not a cookie
@@ -36,13 +46,13 @@ func SessionSecret() string {
3646
/*SessionKeyUser is the random seed for key name for User Id.
3747
*/
3848
func SessionKeyUser() string {
39-
return "FXtlPiHcOtUHJ5Z7u_sw5CvdO23LAbvHhNeUmzqC59N5gmffoHki4-mIdbUb89Qw"
49+
return os.Getenv("HLC_SESSION_KEY_USER")
4050
}
4151

4252
/*SessionKeySID is the key for session id
4353
*/
4454
func SessionKeySID() string {
45-
return "ogIbzGvEnFY2XfuQsLXv6a38dF49tvMuum4R27abuY5xAv0xF2Hc3SXkGoIcbWqd"
55+
return os.Getenv("HLC_SESSION_KEY_SID");
4656
}
4757

4858
/*SessionValidHours defines how long a session could be idle for.
@@ -59,7 +69,7 @@ func GoogleSignInAppID() string {
5969
}
6070

6171
/*GoogleOAuthRedirectURL returns the OAuth2.0 redirect URL for 3-legged authentication.
62-
(Currently this is feature is not supported)
72+
(Currently this is feature is not implemented)
6373
*/
6474
func GoogleOAuthRedirectURL() string {
6575
return "http://127.0.0.1:5556/auth/google/callback"
@@ -68,3 +78,39 @@ func GoogleOAuthRedirectURL() string {
6878
func LoginURL() string {
6979
return "/static/login.html"
7080
}
81+
82+
func RedirectToLogin(next string, w http.ResponseWriter, r *http.Request) {
83+
RedirectTo(LoginURL(), next, w, r)
84+
}
85+
86+
func RedirectTo(redirectUrl string, next_url string, w http.ResponseWriter, r *http.Request) {
87+
log.Debug("Redirecting to ", redirectUrl, " -> ", next_url)
88+
http.SetCookie(w, &http.Cookie{ Name: _HLC_NEXT, Value: next_url, Path: "/" })
89+
http.Redirect(w, r, redirectUrl, http.StatusSeeOther)
90+
}
91+
92+
func SetNext(w http.ResponseWriter, u *url.URL) {
93+
encodedPath := EncodePath(u)
94+
http.SetCookie(w, &http.Cookie{ Name: _HLC_NEXT, Value: encodedPath, Path: "/" })
95+
}
96+
97+
func GetNext(r *http.Request) (string, error) {
98+
var (
99+
err error
100+
c *http.Cookie
101+
)
102+
if c, err = r.Cookie(_HLC_NEXT) ; err == nil {
103+
return c.Value, err
104+
}
105+
return "", err
106+
}
107+
108+
func EncodePath(u *url.URL) string {
109+
log.Debug("Encoding:", u)
110+
path := u.Path
111+
if u.RawQuery != "" {
112+
path += "%3F"
113+
path += u.RawQuery
114+
}
115+
return path
116+
}

controller/app.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/go-playground/log"
1010
"github.com/lpimem/hlcsrv/auth"
11+
"github.com/lpimem/hlcsrv/security"
1112
"github.com/lpimem/hlcsrv/conf"
1213
"github.com/lpimem/hlcsrv/hlccookie"
1314
"github.com/lpimem/hlcsrv/storage"
@@ -73,6 +74,7 @@ func Logout(w http.ResponseWriter, req *http.Request) {
7374
if err != nil {
7475
log.Error(err)
7576
}
77+
conf.RedirectTo("/", "", w, req)
7678
}
7779

7880
// GetPagenote handles get request to fetch notes for a user and a url
@@ -94,8 +96,12 @@ func Logout(w http.ResponseWriter, req *http.Request) {
9496
// 1. hlc_resp.proto https://github.com/lpimem/hlcproto/blob/e7787d65aea33d1eb97b3f1f208394ee6a59f187/hlc_resp.proto
9597
func GetPagenote(w http.ResponseWriter, req *http.Request) {
9698
defer log.WithTrace().Info("GetPagenote...")
99+
security.EnableCORS(w)
100+
if (isHTTPOption(req)) {
101+
return
102+
}
97103
if !requireAuth(w, req) {
98-
log.Warn("Not authorized...")
104+
log.Warn("GetPagenote: not authorized...")
99105
return
100106
}
101107
pn, err := parseGetNotesRequest(req)
@@ -120,10 +126,16 @@ func GetPagenote(w http.ResponseWriter, req *http.Request) {
120126
// Serialized @hlcmsg.HlcResp message encoded in base64.
121127
func SavePagenote(w http.ResponseWriter, req *http.Request) {
122128
defer log.WithTrace().Info("SavePagenote...")
129+
security.EnableCORS(w)
130+
if (isHTTPOption(req)) {
131+
return
132+
}
123133
if !requirePost(w, req) {
134+
log.Warn("SavePagenote: invalid http method...")
124135
return
125136
}
126137
if !requireAuth(w, req) {
138+
log.Warn("SavePagenote: not authorized...")
127139
return
128140
}
129141
pn, err := parseNewNotesRequest(req)
@@ -148,10 +160,16 @@ func SavePagenote(w http.ResponseWriter, req *http.Request) {
148160
// DeletePagenote handles the post request to delete an array of notes.
149161
func DeletePagenote(w http.ResponseWriter, req *http.Request) {
150162
defer log.WithTrace().Info("DeletePagenote...")
163+
security.EnableCORS(w)
164+
if (isHTTPOption(req)) {
165+
return
166+
}
151167
if !requirePost(w, req) {
168+
log.Warn("DeletePagenote: invalid http method...")
152169
return
153170
}
154171
if !requireAuth(w, req) {
172+
log.Warn("SavePagenote: not authorized...")
155173
return
156174
}
157175
idList := parseRemoveNotesRequest(req)

controller/app_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,9 @@ func TestLogout(t *testing.T) {
275275
}
276276
resp := httptest.NewRecorder()
277277
Logout(resp, r)
278-
if resp.Code != http.StatusOK {
279-
t.Errorf("response code is not OK: %d", resp.Code)
278+
expected := http.StatusSeeOther
279+
if resp.Code != expected {
280+
t.Errorf("response code is not %d: %d", expected, resp.Code)
280281
t.Fail()
281282
}
282283

@@ -289,7 +290,8 @@ func TestLogout(t *testing.T) {
289290
t.Error("session still exists after logout: ", lastAccess)
290291
t.Fail()
291292
}
292-
if r, err = auth.Authenticate(r); err != nil {
293+
w := httptest.NewRecorder()
294+
if r, _, err = auth.Authenticate(r, w); err != nil {
293295
t.Error("Authenticate error", err)
294296
t.Fail()
295297
}

controller/hlcimpl.go

+4-14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/base64"
55
"io/ioutil"
66
"net/http"
7-
"net/url"
87
"strconv"
98

109
"github.com/go-playground/log"
@@ -33,9 +32,8 @@ func parseGetNotesRequest(r *http.Request) (*hlcmsg.Pagenote, error) {
3332
err error
3433
)
3534
params := r.URL.Query()
36-
if uid, err = strconv.ParseUint(
37-
params.Get("uid"), 10, 32); err != nil {
38-
log.Error("cannot extract uid from request ", err)
35+
if uid, err = strconv.ParseUint(params.Get("uid"), 10, 32); err != nil {
36+
defer log.WithTrace().Error("cannot extract uid from request ", err)
3937
return nil, err
4038
}
4139
if pid, err = strconv.ParseUint(
@@ -102,13 +100,8 @@ func requirePost(w http.ResponseWriter, r *http.Request) bool {
102100
return false
103101
}
104102

105-
func encodePath(u *url.URL) string {
106-
path := u.Path
107-
if u.RawQuery != "" {
108-
path += "%3F"
109-
path += u.RawQuery
110-
}
111-
return path
103+
func isHTTPOption(r *http.Request) bool {
104+
return r.Method == http.MethodOptions
112105
}
113106

114107
func requireAuth(w http.ResponseWriter, r *http.Request) bool {
@@ -121,9 +114,6 @@ func requireAuth(w http.ResponseWriter, r *http.Request) bool {
121114
errMsg = errMsg + ": " + reason.(string)
122115
}
123116
log.Warn(errMsg)
124-
nextPage := encodePath(r.URL)
125-
loginUrl := conf.LoginURL() + "?" + nextPage
126-
http.Redirect(w, r, loginUrl, http.StatusSeeOther)
127117
authorized = false
128118
}
129119
return authorized

0 commit comments

Comments
 (0)