adjusted tests for new auth lib
This commit is contained in:
@@ -133,8 +133,8 @@ _this is the recommended way to run remark42_
|
||||
| edit-time | EDIT_TIME | `5m` | edit window |
|
||||
| read-age | READONLY_AGE | | read-only age of comments, days |
|
||||
| img-proxy | IMG_PROXY | `false` | enable http->https proxy for images |
|
||||
| admin-passwd | ADMIN_PASSWD | | password for `admin` basic auth |
|
||||
| dbg | DEBUG | `false` | debug mode |
|
||||
| dev-passwd | DEV_PASSWD | | password for `dev` user |
|
||||
|
||||
* command line parameters are long form `--<key>=value`, i.e. `--site=https://demo.remark42.com`
|
||||
* _multi_ parameters separated by `,` in the environment or repeated with command line key, like `--site=s1 --site=s2 ...`
|
||||
|
||||
Generated
+5
-5
@@ -113,7 +113,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5ef69525e5e62fb771f3f6910c94030a86b542eff1cb9d9350803b6dae147144"
|
||||
digest = "1:b117a0a0b46dad26254a48c11a511d6c697038e591a1d7ce11a229e1c8e0a237"
|
||||
name = "github.com/go-pkgz/auth"
|
||||
packages = [
|
||||
".",
|
||||
@@ -123,7 +123,7 @@
|
||||
"token",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "8d5238712a320d972f9d658e2fd1d4468ef81c3e"
|
||||
revision = "b95cb645615503dba4d5fced3b77d97d4f0dcc81"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1212e114344a5cdcc834ea69e19d456eef230f9784659080fee67e02ba2cb574"
|
||||
@@ -145,7 +145,7 @@
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:71dc1e5b19e179495d2e2ca63454a9204753c5ecb3faa4a842ea5859355a968f"
|
||||
digest = "1:e133aa7be09588b02198e4ddb98df5033b0319b56533881d0163ee51b903305b"
|
||||
name = "github.com/go-pkgz/rest"
|
||||
packages = [
|
||||
".",
|
||||
@@ -153,8 +153,8 @@
|
||||
"logger",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "c0e09a7a640e54001aed8bad117d60ad8971958e"
|
||||
version = "v1.1.1"
|
||||
revision = "553c0e1b55b215f8f55da4682ac57aff9aec8b6d"
|
||||
version = "v1.1.5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ffc060c551980d37ee9e428ef528ee2813137249ccebb0bfc412ef83071cac91"
|
||||
|
||||
@@ -43,7 +43,7 @@ type ServerCommand struct {
|
||||
SSL SSLGroup `group:"ssl" namespace:"ssl" env-namespace:"SSL"`
|
||||
|
||||
Sites []string `long:"site" env:"SITE" default:"remark" description:"site names" env-delim:","`
|
||||
DevPasswd string `long:"dev-passwd" env:"DEV_PASSWD" default:"" description:"development mode password"`
|
||||
AdminPasswd string `long:"admin-passwd" env:"ADMIN_PASSWD" default:"" description:"admin basic auth password"`
|
||||
BackupLocation string `long:"backup" env:"BACKUP_PATH" default:"./var/backup" description:"backups location"`
|
||||
MaxBackupFiles int `long:"max-back" env:"MAX_BACKUP_FILES" default:"10" description:"max backups to keep"`
|
||||
ImageProxy bool `long:"img-proxy" env:"IMG_PROXY" description:"enable image proxy"`
|
||||
@@ -293,8 +293,8 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
|
||||
// Run all application objects
|
||||
func (a *serverApp) run(ctx context.Context) error {
|
||||
if a.DevPasswd != "" {
|
||||
log.Printf("[WARN] running in dev mode")
|
||||
if a.AdminPasswd != "" {
|
||||
log.Printf("[WARN] admin basic auth enabled")
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -537,7 +537,7 @@ func (s *ServerCommand) makeAuthenticator(ds *service.DataStore, avas avatar.Sto
|
||||
c.User.SetBoolAttr("blocked", ds.IsBlocked(c.Audience, c.User.ID))
|
||||
return c
|
||||
}),
|
||||
DevPasswd: s.DevPasswd,
|
||||
AdminPasswd: s.AdminPasswd,
|
||||
Validator: token.ValidatorFunc(func(token string, claims token.Claims) bool { // check on each auth call (in middleware)
|
||||
if claims.User == nil {
|
||||
return false
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestServerApp(t *testing.T) {
|
||||
client := http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest("POST", "http://localhost:18080/api/v1/comment",
|
||||
strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "remark"}}`))
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
require.Nil(t, err)
|
||||
resp, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
@@ -58,7 +58,7 @@ func TestServerApp(t *testing.T) {
|
||||
func TestServerApp_DevMode(t *testing.T) {
|
||||
app, ctx := prepServerApp(t, 500*time.Millisecond, func(o ServerCommand) ServerCommand {
|
||||
o.Port = 18085
|
||||
o.DevPasswd = "password"
|
||||
o.AdminPasswd = "password"
|
||||
o.Auth.Dev = true
|
||||
return o
|
||||
})
|
||||
@@ -95,7 +95,7 @@ func TestServerApp_WithMongo(t *testing.T) {
|
||||
|
||||
// prepare options
|
||||
p := flags.NewParser(&opts, flags.Default)
|
||||
_, err := p.ParseArgs([]string{"--dev-passwd=password", "--cache.type=none", "--store.type=mongo",
|
||||
_, err := p.ParseArgs([]string{"--admin-passwd=password", "--cache.type=none", "--store.type=mongo",
|
||||
"--avatar.type=mongo", "--mongo.url=" + mongoURL, "--mongo.db=test_remark", "--port=12345", "--admin.type=mongo"})
|
||||
require.Nil(t, err)
|
||||
opts.Auth.Github.CSEC, opts.Auth.Github.CID = "csec", "cid"
|
||||
@@ -142,7 +142,7 @@ func TestServerApp_WithSSL(t *testing.T) {
|
||||
|
||||
// prepare options
|
||||
p := flags.NewParser(&opts, flags.Default)
|
||||
_, err := p.ParseArgs([]string{"--dev-passwd=password", "--port=18080", "--store.bolt.path=/tmp/xyz", "--backup=/tmp", "--avatar.type=bolt", "--avatar.bolt.file=/tmp/ava-test.db", "--notify.type=none",
|
||||
_, err := p.ParseArgs([]string{"--admin-passwd=password", "--port=18080", "--store.bolt.path=/tmp/xyz", "--backup=/tmp", "--avatar.type=bolt", "--avatar.bolt.file=/tmp/ava-test.db", "--notify.type=none",
|
||||
"--ssl.type=static", "--ssl.cert=testdata/cert.pem", "--ssl.key=testdata/key.pem", "--ssl.port=18443"})
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -318,7 +318,7 @@ func prepServerApp(t *testing.T, duration time.Duration, fn func(o ServerCommand
|
||||
|
||||
// prepare options
|
||||
p := flags.NewParser(&cmd, flags.Default)
|
||||
_, err := p.ParseArgs([]string{"--dev-passwd=password"})
|
||||
_, err := p.ParseArgs([]string{"--admin-passwd=password"})
|
||||
require.Nil(t, err)
|
||||
cmd.Avatar.FS.Path, cmd.Avatar.Type, cmd.BackupLocation = "/tmp", "fs", "/tmp"
|
||||
cmd.Store.Bolt.Path = fmt.Sprintf("/tmp/%d", cmd.Port)
|
||||
|
||||
@@ -120,7 +120,7 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if claims.User.Picture != "" {
|
||||
if claims.User.Picture != "" && a.authenticator.AvatarProxy() != nil {
|
||||
avatartStore := a.authenticator.AvatarProxy().Store
|
||||
if err := avatartStore.Remove(path.Base(claims.User.Picture)); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user's avatar")
|
||||
|
||||
@@ -23,9 +23,8 @@ import (
|
||||
)
|
||||
|
||||
func TestAdmin_Delete(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", User: store.User{ID: "id", Name: "name"},
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
|
||||
@@ -39,12 +38,12 @@ func TestAdmin_Delete(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodDelete,
|
||||
fmt.Sprintf("%s/api/v1/admin/comment/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
body, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
|
||||
body, code := getWithDevAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
|
||||
assert.Equal(t, 200, code)
|
||||
cr := store.Comment{}
|
||||
err = json.Unmarshal([]byte(body), &cr)
|
||||
@@ -54,9 +53,8 @@ func TestAdmin_Delete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_DeleteUser(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Orig: "o test test #1", User: store.User{ID: "id1", Name: "name"},
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
|
||||
@@ -76,7 +74,7 @@ func TestAdmin_DeleteUser(t *testing.T) {
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/admin/user/%s?site=radio-t", ts.URL, "id2"), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
@@ -108,9 +106,8 @@ func TestAdmin_DeleteUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_Pin(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
|
||||
@@ -125,7 +122,7 @@ func TestAdmin_Pin(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/pin/%s?site=radio-t&url=https://radio-t.com/blah&pin=%d", ts.URL, id1, val), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
return resp.StatusCode
|
||||
@@ -152,9 +149,8 @@ func TestAdmin_Pin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_Block(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}}
|
||||
@@ -174,7 +170,7 @@ func TestAdmin_Block(t *testing.T) {
|
||||
}
|
||||
req, e := http.NewRequest(http.MethodPut, url, nil)
|
||||
assert.Nil(t, e)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, e := client.Do(req)
|
||||
require.Nil(t, e)
|
||||
body, e = ioutil.ReadAll(resp.Body)
|
||||
@@ -233,9 +229,8 @@ func TestAdmin_Block(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_BlockedList(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
client := http.Client{}
|
||||
|
||||
@@ -243,7 +238,7 @@ func TestAdmin_BlockedList(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/user/%s?site=radio-t&block=%d", ts.URL, "user1", 1), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
_, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -251,33 +246,40 @@ func TestAdmin_BlockedList(t *testing.T) {
|
||||
req, err = http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/user/%s?site=radio-t&block=%d&ttl=50ms", ts.URL, "user2", 1), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
_, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
|
||||
res, code := getWithAuth(t, ts.URL+"/api/v1/admin/blocked?site=radio-t")
|
||||
require.Equal(t, 200, code, res)
|
||||
req, err = http.NewRequest("GET", ts.URL+"/api/v1/admin/blocked?site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
res, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 200, res.StatusCode)
|
||||
users := []store.BlockedUser{}
|
||||
err = json.Unmarshal([]byte(res), &users)
|
||||
err = json.NewDecoder(res.Body).Decode(&users)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(users), "two users blocked")
|
||||
assert.Equal(t, "user1", users[0].ID)
|
||||
assert.Equal(t, "user2", users[1].ID)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
res, code = getWithAuth(t, ts.URL+"/api/v1/admin/blocked?site=radio-t")
|
||||
require.Equal(t, 200, code, res)
|
||||
|
||||
req, err = http.NewRequest("GET", ts.URL+"/api/v1/admin/blocked?site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
res, err = client.Do(req)
|
||||
require.Equal(t, 200, res.StatusCode)
|
||||
users = []store.BlockedUser{}
|
||||
err = json.Unmarshal([]byte(res), &users)
|
||||
err = json.NewDecoder(res.Body).Decode(&users)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(users), "one user left blocked")
|
||||
|
||||
}
|
||||
|
||||
func TestAdmin_ReadOnly(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}}
|
||||
@@ -299,7 +301,7 @@ func TestAdmin_ReadOnly(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah&ro=1", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
@@ -314,7 +316,7 @@ func TestAdmin_ReadOnly(t *testing.T) {
|
||||
assert.Nil(t, err, "can't marshal comment %+v", c)
|
||||
req, err = http.NewRequest("POST", ts.URL+"/api/v1/comment", bytes.NewBuffer(b))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
@@ -323,7 +325,7 @@ func TestAdmin_ReadOnly(t *testing.T) {
|
||||
req, err = http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah&ro=0", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
require.Nil(t, err)
|
||||
@@ -338,16 +340,15 @@ func TestAdmin_ReadOnly(t *testing.T) {
|
||||
assert.Nil(t, err, "can't marshal comment %+v", c)
|
||||
req, err = http.NewRequest("POST", ts.URL+"/api/v1/comment", bytes.NewBuffer(b))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestAdmin_ReadOnlyWithAge(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"},
|
||||
@@ -365,7 +366,7 @@ func TestAdmin_ReadOnlyWithAge(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah&ro=1", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
@@ -377,7 +378,7 @@ func TestAdmin_ReadOnlyWithAge(t *testing.T) {
|
||||
req, err = http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah&ro=0", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
require.Nil(t, err)
|
||||
@@ -387,9 +388,8 @@ func TestAdmin_ReadOnlyWithAge(t *testing.T) {
|
||||
|
||||
}
|
||||
func TestAdmin_Verify(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}}
|
||||
@@ -408,7 +408,7 @@ func TestAdmin_Verify(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/verify/user1?site=radio-t&verified=1", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
_, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
verified = srv.DataService.IsVerified("radio-t", "user1")
|
||||
@@ -426,7 +426,7 @@ func TestAdmin_Verify(t *testing.T) {
|
||||
req, err = http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/verify/user1?site=radio-t&verified=0", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
_, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
verified = srv.DataService.IsVerified("radio-t", "user1")
|
||||
@@ -443,9 +443,8 @@ func TestAdmin_Verify(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_ExportStream(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -455,7 +454,7 @@ func TestAdmin_ExportStream(t *testing.T) {
|
||||
addComment(t, c1, ts)
|
||||
addComment(t, c2, ts)
|
||||
|
||||
body, code := getWithAuth(t, ts.URL+"/api/v1/admin/export?site=radio-t&mode=stream")
|
||||
body, code := getWithAdminAuth(t, ts.URL+"/api/v1/admin/export?site=radio-t&mode=stream")
|
||||
assert.Equal(t, 200, code)
|
||||
assert.Equal(t, 3, strings.Count(body, "\n"))
|
||||
assert.Equal(t, 2, strings.Count(body, "\"text\""))
|
||||
@@ -463,9 +462,8 @@ func TestAdmin_ExportStream(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_ExportFile(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -478,7 +476,7 @@ func TestAdmin_ExportFile(t *testing.T) {
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest("GET", ts.URL+"/api/v1/admin/export?site=radio-t&mode=file", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -495,9 +493,8 @@ func TestAdmin_ExportFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_DeleteMeRequest(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}}
|
||||
@@ -531,9 +528,8 @@ func TestAdmin_DeleteMeRequest(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
_ = os.MkdirAll("/tmp/42", 0700)
|
||||
defer func() { _ = os.RemoveAll("/tmp/42") }()
|
||||
require.NoError(t, ioutil.WriteFile("/tmp/42/pic.image", []byte("some image data"), 0600))
|
||||
require.NoError(t, os.MkdirAll("/tmp/ava-remark42/42", 0700))
|
||||
require.NoError(t, ioutil.WriteFile("/tmp/ava-remark42/42/pic.image", []byte("some image data"), 0600))
|
||||
|
||||
tkn, err := srv.Authenticator.TokenService().Token(claims)
|
||||
assert.Nil(t, err)
|
||||
@@ -541,9 +537,9 @@ func TestAdmin_DeleteMeRequest(t *testing.T) {
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/admin/deleteme?token=%s", ts.URL, tkn), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
_, err = srv.DataService.User("radio-t", "user1", 0, 0)
|
||||
@@ -551,9 +547,8 @@ func TestAdmin_DeleteMeRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}}
|
||||
@@ -569,7 +564,7 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/admin/deleteme?token=%s", ts.URL, "bad token"), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 400, resp.StatusCode)
|
||||
@@ -596,7 +591,7 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/admin/deleteme?token=%s", ts.URL, tkn), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "bad-password")
|
||||
req.SetBasicAuth("admin", "bad-password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
@@ -608,7 +603,7 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/admin/deleteme?token=%s", ts.URL, tkn), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 400, resp.StatusCode, resp.Status)
|
||||
@@ -620,7 +615,7 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/admin/deleteme?token=%s", ts.URL, tkn), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
@@ -630,9 +625,8 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdmin_GetUserInfo(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
URL: "https://radio-t.com/blah"}, User: store.User{Name: "user1 name", ID: "user1"}}
|
||||
@@ -644,7 +638,7 @@ func TestAdmin_GetUserInfo(t *testing.T) {
|
||||
_, err = srv.DataService.Create(c2)
|
||||
assert.Nil(t, err)
|
||||
|
||||
body, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/admin/user/user1?site=radio-t&url=https://radio-t.com/blah", ts.URL))
|
||||
body, code := getWithAdminAuth(t, fmt.Sprintf("%s/api/v1/admin/user/user1?site=radio-t&url=https://radio-t.com/blah", ts.URL))
|
||||
assert.Equal(t, 200, code)
|
||||
u := store.User{}
|
||||
err = json.Unmarshal([]byte(body), &u)
|
||||
@@ -655,6 +649,6 @@ func TestAdmin_GetUserInfo(t *testing.T) {
|
||||
_, code = get(t, fmt.Sprintf("%s/api/v1/admin/user/user1?site=radio-t&url=https://radio-t.com/blah", ts.URL))
|
||||
assert.Equal(t, 401, code, "no auth")
|
||||
|
||||
_, code = getWithAuth(t, fmt.Sprintf("%s/api/v1/admin/user/userX?site=radio-t&url=https://radio-t.com/blah", ts.URL))
|
||||
_, code = getWithAdminAuth(t, fmt.Sprintf("%s/api/v1/admin/user/userX?site=radio-t&url=https://radio-t.com/blah", ts.URL))
|
||||
assert.Equal(t, 400, code, "no info about user")
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
@@ -16,6 +15,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-pkgz/auth/token"
|
||||
|
||||
bolt "github.com/coreos/bbolt"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-pkgz/auth"
|
||||
@@ -39,7 +40,8 @@ func TestMigrator_Import(t *testing.T) {
|
||||
{"id":"83fd97fd-ff64-48d1-9fb7-ca7769c77037","pid":"p1","text":"<p>test test #2</p>","user":{"name":"developer one","id":"dev","picture":"/api/v1/avatar/remark.image","profile":"https://remark42.com","admin":true,"ip":"ae12fe3b5f129b5cc4cdd2b136b7b7947c4d2741"},"locator":{"site":"radio-t","url":"https://radio-t.com/blah2"},"score":0,"votes":{},"time":"2018-04-30T01:37:00.861387771-05:00"}`)
|
||||
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r)
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native", r)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.Nil(t, err)
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
@@ -51,7 +53,7 @@ func TestMigrator_Import(t *testing.T) {
|
||||
|
||||
client = &http.Client{Timeout: 10 * time.Second}
|
||||
req, err = http.NewRequest("GET", ts.URL+"/import/wait?site=radio-t", nil)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.NoError(t, err)
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
@@ -74,7 +76,8 @@ func TestMigrator_ImportForm(t *testing.T) {
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
require.NoError(t, bodyWriter.Close())
|
||||
|
||||
resp, err := http.Post(ts.URL+"/import/form?site=radio-t&provider=native&secret=123456", contentType, bodyBuf)
|
||||
authts := strings.Replace(ts.URL, "http://", "http://admin:password@", 1)
|
||||
resp, err := http.Post(authts+"/import/form?site=radio-t&provider=native", contentType, bodyBuf)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||
|
||||
@@ -84,7 +87,7 @@ func TestMigrator_ImportForm(t *testing.T) {
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
req, err := http.NewRequest("GET", ts.URL+"/import/wait?site=radio-t", nil)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.NoError(t, err)
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
@@ -97,9 +100,10 @@ func TestMigrator_ImportFromWP(t *testing.T) {
|
||||
r := strings.NewReader(strings.Replace(xmlTestWP, "'", "`", -1))
|
||||
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=wordpress&secret=123456", r)
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=wordpress", r)
|
||||
assert.Nil(t, err)
|
||||
req.Header.Add("Content-Type", "application/xml; charset=utf-8")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||
@@ -110,16 +114,15 @@ func TestMigrator_ImportFromWP(t *testing.T) {
|
||||
|
||||
client = &http.Client{Timeout: 10 * time.Second}
|
||||
req, err = http.NewRequest("GET", ts.URL+"/import/wait?site=radio-t", nil)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.NoError(t, err)
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
assert.NoError(t, ds.Interface.Close())
|
||||
|
||||
srvAccess, tsAccess := prep(t)
|
||||
require.NotNil(t, srvAccess)
|
||||
defer cleanup(ts, srvAccess)
|
||||
tsAccess, _, teardownAccess := startupT(t)
|
||||
defer teardownAccess()
|
||||
|
||||
res, code := get(t, tsAccess.URL+"/api/v1/last/10?site=radio-t")
|
||||
require.Equal(t, 200, code)
|
||||
@@ -160,14 +163,16 @@ func TestMigrator_ImportDouble(t *testing.T) {
|
||||
}
|
||||
r := strings.NewReader(`{"version":1}` + strings.Join(recs, "\n")) // reader with 10k records
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r)
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native", r)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.Nil(t, err)
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||
|
||||
client = &http.Client{Timeout: 1 * time.Second}
|
||||
req, err = http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r)
|
||||
req, err = http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native", r)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.Nil(t, err)
|
||||
resp, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
@@ -187,7 +192,8 @@ func TestMigrator_ImportWaitExpired(t *testing.T) {
|
||||
}
|
||||
r := strings.NewReader(`{"version":1}` + strings.Join(recs, "\n")) // reader with 10k records
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r)
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native", r)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
require.Nil(t, err)
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
@@ -195,7 +201,7 @@ func TestMigrator_ImportWaitExpired(t *testing.T) {
|
||||
|
||||
client = &http.Client{Timeout: 10 * time.Second}
|
||||
req, err = http.NewRequest("GET", ts.URL+"/import/wait?site=radio-t&timeout=100ms", nil)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.NoError(t, err)
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, http.StatusGatewayTimeout, resp.StatusCode)
|
||||
@@ -211,21 +217,23 @@ func TestMigrator_Export(t *testing.T) {
|
||||
|
||||
// import comments first
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native&secret=123456", r)
|
||||
req, err := http.NewRequest("POST", ts.URL+"/import?site=radio-t&provider=native", r)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||
client = &http.Client{Timeout: 10 * time.Second}
|
||||
req, err = http.NewRequest("GET", ts.URL+"/import/wait?site=radio-t", nil)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
assert.NoError(t, err)
|
||||
resp, err = client.Do(req)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
// check file mode
|
||||
req, err = http.NewRequest("GET", ts.URL+"/export?mode=file&site=radio-t&secret=123456", nil)
|
||||
req, err = http.NewRequest("GET", ts.URL+"/export?mode=file&site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
@@ -240,8 +248,9 @@ func TestMigrator_Export(t *testing.T) {
|
||||
t.Logf("%s", string(ungzBody))
|
||||
|
||||
// check stream mode
|
||||
req, err = http.NewRequest("GET", ts.URL+"/export?mode=stream&site=radio-t&secret=123456", nil)
|
||||
req, err = http.NewRequest("GET", ts.URL+"/export?mode=stream&site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
@@ -253,7 +262,7 @@ func TestMigrator_Export(t *testing.T) {
|
||||
assert.Equal(t, 2, strings.Count(string(body), "\"text\""))
|
||||
t.Logf("%s", string(body))
|
||||
|
||||
req, err = http.NewRequest("GET", ts.URL+"/export?site=radio-t&secret=bad", nil)
|
||||
req, err = http.NewRequest("GET", ts.URL+"/export?site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
resp, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
@@ -275,7 +284,7 @@ func prepImportSrv(t *testing.T) (svc *Migrator, ds *service.DataStore, ts *http
|
||||
}
|
||||
|
||||
a := auth.NewService(auth.Opts{
|
||||
DevPasswd: "password",
|
||||
AdminPasswd: "password",
|
||||
SecretReader: token.SecretFunc(func(id string) (string, error) { return "123456", nil }),
|
||||
Issuer: "test",
|
||||
})
|
||||
|
||||
@@ -18,9 +18,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRest_Create(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
resp, err := post(t, ts.URL+"/api/v1/comment",
|
||||
`{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
|
||||
@@ -29,6 +28,7 @@ func TestRest_Create(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
require.Equal(t, http.StatusCreated, resp.StatusCode, string(b))
|
||||
|
||||
t.Log(string(b))
|
||||
c := R.JSON{}
|
||||
err = json.Unmarshal(b, &c)
|
||||
assert.Nil(t, err)
|
||||
@@ -39,9 +39,8 @@ func TestRest_Create(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_CreateOldPost(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
// make old, but not too old comment
|
||||
old := store.Comment{Text: "test test old", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -5),
|
||||
@@ -73,9 +72,8 @@ func TestRest_CreateOldPost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_CreateTooBig(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
longComment := fmt.Sprintf(`{"text": "%4001s", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`, "Щ")
|
||||
|
||||
@@ -105,9 +103,8 @@ func TestRest_CreateTooBig(t *testing.T) {
|
||||
|
||||
func TestRest_CreateRejected(t *testing.T) {
|
||||
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
body := `{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`
|
||||
|
||||
// try to create without auth
|
||||
@@ -117,9 +114,8 @@ func TestRest_CreateRejected(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_CreateAndGet(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
// create comment
|
||||
resp, err := post(t, ts.URL+"/api/v1/comment",
|
||||
@@ -134,24 +130,31 @@ func TestRest_CreateAndGet(t *testing.T) {
|
||||
|
||||
id := c["id"].(string)
|
||||
|
||||
// get created comment by id
|
||||
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
// get created comment by id as admin
|
||||
res, code := getWithAdminAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
assert.Equal(t, 200, code)
|
||||
comment := store.Comment{}
|
||||
err = json.Unmarshal([]byte(res), &comment)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "<p><strong>test</strong> <em>123</em></p>\n\n<p><a href=\"http://radio-t.com\" rel=\"nofollow\">http://radio-t.com</a></p>\n", comment.Text)
|
||||
assert.Equal(t, "**test** *123*\n\n http://radio-t.com", comment.Orig)
|
||||
assert.Equal(t, store.User{Name: "developer one", ID: "dev", Admin: true, Blocked: false,
|
||||
assert.Equal(t, store.User{Name: "admin", ID: "admin", Admin: true, Blocked: false,
|
||||
IP: "dbc7c999343f003f189f70aaf52cc04443f90790"},
|
||||
comment.User)
|
||||
t.Logf("%+v", comment)
|
||||
|
||||
// get created comment by id as non-admin
|
||||
res, code = getWithDevAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
assert.Equal(t, 200, code)
|
||||
comment = store.Comment{}
|
||||
err = json.Unmarshal([]byte(res), &comment)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, store.User{Name: "admin", ID: "admin", Admin: true, Blocked: false, IP: ""}, comment.User, "no ip")
|
||||
}
|
||||
|
||||
func TestRest_Update(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -161,7 +164,7 @@ func TestRest_Update(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1",
|
||||
strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
b, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
body, err := ioutil.ReadAll(b.Body)
|
||||
@@ -179,7 +182,7 @@ func TestRest_Update(t *testing.T) {
|
||||
assert.True(t, time.Since(c2.Edit.Timestamp) < 1*time.Second)
|
||||
|
||||
// read updated comment
|
||||
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
res, code := getWithAdminAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
assert.Equal(t, 200, code)
|
||||
c3 := store.Comment{}
|
||||
err = json.Unmarshal([]byte(res), &c3)
|
||||
@@ -188,9 +191,8 @@ func TestRest_Update(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_UpdateDelete(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -200,7 +202,7 @@ func TestRest_UpdateDelete(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id+"?site=radio-t&url=https://radio-t.com/blah1",
|
||||
strings.NewReader(`{"delete": true, "summary":"removed by user"}`))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
b, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
body, err := ioutil.ReadAll(b.Body)
|
||||
@@ -215,7 +217,7 @@ func TestRest_UpdateDelete(t *testing.T) {
|
||||
assert.True(t, c2.Deleted)
|
||||
|
||||
// read updated comment
|
||||
res, code := getWithAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
res, code := getWithDevAuth(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah1", ts.URL, id))
|
||||
assert.Equal(t, 200, code)
|
||||
c3 := store.Comment{}
|
||||
err = json.Unmarshal([]byte(res), &c3)
|
||||
@@ -223,13 +225,11 @@ func TestRest_UpdateDelete(t *testing.T) {
|
||||
assert.Equal(t, "", c3.Text)
|
||||
assert.Equal(t, "", c3.Orig)
|
||||
assert.True(t, c3.Deleted)
|
||||
|
||||
}
|
||||
|
||||
func TestRest_UpdateNotOwner(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "xyz"}}
|
||||
@@ -240,7 +240,7 @@ func TestRest_UpdateNotOwner(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
|
||||
"?site=radio-t&url=https://radio-t.com/blah1", strings.NewReader(`{"text":"updated text", "summary":"my edit"}`))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
b, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
body, err := ioutil.ReadAll(b.Body)
|
||||
@@ -252,16 +252,15 @@ func TestRest_UpdateNotOwner(t *testing.T) {
|
||||
req, err = http.NewRequest(http.MethodPut, ts.URL+"/api/v1/comment/"+id1+
|
||||
"?site=radio-t&url=https://radio-t.com/blah1", strings.NewReader(`ERRR "text":"updated text", "summary":"my"}`))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
b, err = client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 400, b.StatusCode, string(body), "update is not json")
|
||||
}
|
||||
|
||||
func TestRest_Vote(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah"}}
|
||||
@@ -276,7 +275,7 @@ func TestRest_Vote(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/vote/%s?site=radio-t&url=https://radio-t.com/blah&vote=%d", ts.URL, id1, val), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
return resp.StatusCode
|
||||
@@ -290,7 +289,7 @@ func TestRest_Vote(t *testing.T) {
|
||||
err := json.Unmarshal([]byte(body), &cr)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, cr.Score)
|
||||
assert.Equal(t, map[string]bool{"dev": true}, cr.Votes)
|
||||
assert.Equal(t, map[string]bool{"admin": true}, cr.Votes)
|
||||
|
||||
assert.Equal(t, 200, vote(-1), "opposite vote allowed")
|
||||
body, code = get(t, fmt.Sprintf("%s/api/v1/id/%s?site=radio-t&url=https://radio-t.com/blah", ts.URL, id1))
|
||||
@@ -303,9 +302,8 @@ func TestRest_Vote(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_UserAllData(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
// write 3 comments
|
||||
user := store.User{ID: "dev", Name: "user name 1"}
|
||||
@@ -325,7 +323,7 @@ func TestRest_UserAllData(t *testing.T) {
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("GET", ts.URL+"/api/v1/userdata?site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
@@ -336,7 +334,7 @@ func TestRest_UserAllData(t *testing.T) {
|
||||
ungzBody, err := ioutil.ReadAll(ungzReader)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.HasPrefix(string(ungzBody),
|
||||
`{"info": {"name":"developer one","id":"dev","picture":"","admin":true}, "comments":[{`))
|
||||
`{"info": {"name":"developer one","id":"dev","picture":"http://example.com/pic.png","ip":"127.0.0.1","admin":false}, "comments":[{`))
|
||||
assert.Equal(t, 3, strings.Count(string(ungzBody), `"text":`), "3 comments inside")
|
||||
t.Logf("%s", string(ungzBody))
|
||||
|
||||
@@ -347,7 +345,8 @@ func TestRest_UserAllData(t *testing.T) {
|
||||
|
||||
err = json.Unmarshal(ungzBody, &parsed)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, store.User{Name: "developer one", ID: "dev", Picture: "", Admin: true}, parsed.Info)
|
||||
assert.Equal(t, store.User{Name: "developer one", ID: "dev",
|
||||
Picture: "http://example.com/pic.png", IP: "127.0.0.1"}, parsed.Info)
|
||||
assert.Equal(t, 3, len(parsed.Comments))
|
||||
|
||||
req, err = http.NewRequest("GET", ts.URL+"/api/v1/userdata?site=radio-t", nil)
|
||||
@@ -358,9 +357,8 @@ func TestRest_UserAllData(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_UserAllDataManyComments(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
user := store.User{ID: "dev", Name: "user name 1"}
|
||||
c := store.Comment{User: user, Text: "test test #1", Locator: store.Locator{SiteID: "radio-t",
|
||||
@@ -376,7 +374,7 @@ func TestRest_UserAllDataManyComments(t *testing.T) {
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
req, err := http.NewRequest("GET", ts.URL+"/api/v1/userdata?site=radio-t", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
@@ -387,19 +385,18 @@ func TestRest_UserAllDataManyComments(t *testing.T) {
|
||||
ungzBody, err := ioutil.ReadAll(ungzReader)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.HasPrefix(string(ungzBody),
|
||||
`{"info": {"name":"developer one","id":"dev","picture":"","admin":true}, "comments":[{`))
|
||||
`{"info": {"name":"developer one","id":"dev","picture":"http://example.com/pic.png","ip":"127.0.0.1","admin":false}, "comments":[{`))
|
||||
assert.Equal(t, 478, strings.Count(string(ungzBody), `"text":`), "478 comments inside")
|
||||
}
|
||||
|
||||
func TestRest_DeleteMe(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
client := http.Client{}
|
||||
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/deleteme?site=radio-t", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err := client.Do(req)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
@@ -18,9 +18,8 @@ import (
|
||||
)
|
||||
|
||||
func TestRest_Ping(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
res, code := get(t, ts.URL+"/api/v1/ping")
|
||||
assert.Equal(t, "pong", res)
|
||||
@@ -28,9 +27,8 @@ func TestRest_Ping(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_Preview(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
resp, err := post(t, ts.URL+"/api/v1/preview", `{"text": "test 123", "locator":{"url": "https://radio-t.com/blah1", "site": "radio-t"}}`)
|
||||
assert.Nil(t, err)
|
||||
@@ -41,9 +39,8 @@ func TestRest_Preview(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_PreviewWithMD(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
require.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
text := `
|
||||
# h1
|
||||
@@ -69,9 +66,8 @@ BKT
|
||||
}
|
||||
|
||||
func TestRest_Find(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
_, code := get(t, ts.URL+"/api/v1/find?site=radio-t&url=https://radio-t.com/blah1")
|
||||
assert.Equal(t, 400, code, "nothing in")
|
||||
@@ -123,9 +119,8 @@ func TestRest_Find(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_FindAge(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -5),
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
|
||||
@@ -155,9 +150,8 @@ func TestRest_FindAge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_FindReadOnly(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "", Timestamp: time.Now().AddDate(0, 0, -1),
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}, User: store.User{ID: "u1"}}
|
||||
@@ -175,7 +169,7 @@ func TestRest_FindReadOnly(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/admin/readonly?site=radio-t&url=https://radio-t.com/blah1&ro=1", ts.URL), nil)
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
_, err = client.Do(req)
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -197,9 +191,8 @@ func TestRest_FindReadOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_Last(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1", ParentID: "p1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -243,9 +236,8 @@ func TestRest_Last(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_FindUserComments(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -280,22 +272,20 @@ func TestRest_FindUserComments(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_UserInfo(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
body, code := getWithAuth(t, ts.URL+"/api/v1/user?site=radio-t")
|
||||
body, code := getWithDevAuth(t, ts.URL+"/api/v1/user?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
user := store.User{}
|
||||
err := json.Unmarshal([]byte(body), &user)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, store.User{Name: "developer one", ID: "dev", Picture: "", Admin: true, Blocked: false, IP: ""}, user)
|
||||
assert.Equal(t, store.User{Name: "developer one", ID: "dev", Picture: "http://example.com/pic.png", IP: "127.0.0.1"}, user)
|
||||
}
|
||||
|
||||
func TestRest_Count(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -323,9 +313,8 @@ func TestRest_Count(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_Counts(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -353,9 +342,8 @@ func TestRest_Counts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_List(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
c1 := store.Comment{Text: "test test #1",
|
||||
Locator: store.Locator{SiteID: "radio-t", URL: "https://radio-t.com/blah1"}}
|
||||
@@ -380,9 +368,8 @@ func TestRest_List(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_Config(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
body, code := get(t, ts.URL+"/api/v1/config?site=radio-t")
|
||||
assert.Equal(t, 200, code)
|
||||
@@ -400,9 +387,8 @@ func TestRest_Config(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_Info(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
srv.ReadOnlyAge = 10000000 // make sure we don't hit read-only
|
||||
|
||||
@@ -438,9 +424,8 @@ func TestRest_Info(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_Robots(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
body, code := get(t, ts.URL+"/robots.txt")
|
||||
assert.Equal(t, 200, code)
|
||||
|
||||
@@ -33,10 +33,11 @@ var testDb = "/tmp/test-remark.db"
|
||||
var testHTML = "/tmp/test-remark.html"
|
||||
var getStartedHTML = "/tmp/getstarted.html"
|
||||
|
||||
var devToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcms0MiIsImV4cCI6Mzc4OTE5MTgyMiwianRpIjoicmFuZG9tIGlkIiwiaXNzIjoicmVtYXJrNDIiLCJuYmYiOjE1MjE4ODQyMjIsInVzZXIiOnsibmFtZSI6ImRldmVsb3BlciBvbmUiLCJpZCI6ImRldiIsInBpY3R1cmUiOiJodHRwOi8vZXhhbXBsZS5jb20vcGljLnBuZyIsImlwIjoiMTI3LjAuMC4xIiwiZW1haWwiOiJtZUBleGFtcGxlLmNvbSJ9fQ.aKUAXiZxXypgV7m1wEOgUcyPOvUDXHDi3A06YWKbcLg"
|
||||
|
||||
func TestRest_FileServer(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
body, code := get(t, ts.URL+"/web/test-remark.html")
|
||||
assert.Equal(t, 200, code)
|
||||
@@ -44,9 +45,8 @@ func TestRest_FileServer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRest_GetStarted(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
err := ioutil.WriteFile(getStartedHTML, []byte("some html blah"), 0700)
|
||||
assert.Nil(t, err)
|
||||
@@ -175,7 +175,7 @@ func TestRest_RunAutocertModeHTTPOnly(t *testing.T) {
|
||||
srv.Shutdown()
|
||||
}
|
||||
|
||||
func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
|
||||
func startupT(t *testing.T) (ts *httptest.Server, srv *Rest, teardown func()) {
|
||||
b, err := engine.NewBoltDB(bolt.Options{}, engine.BoltSite{FileName: testDb, SiteID: "radio-t"})
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -192,10 +192,9 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
|
||||
srv = &Rest{
|
||||
DataService: dataStore,
|
||||
Authenticator: auth.NewService(auth.Opts{
|
||||
DevPasswd: "password",
|
||||
SecretReader: token.SecretFunc(func(id string) (string, error) { return "secret", nil }),
|
||||
AvatarStore: avatar.NewLocalFS("/tmp"),
|
||||
AvatarResizeLimit: 300,
|
||||
AdminPasswd: "password",
|
||||
SecretReader: token.SecretFunc(func(id string) (string, error) { return "secret", nil }),
|
||||
AvatarStore: avatar.NewLocalFS("/tmp/ava-remark42"),
|
||||
}),
|
||||
Cache: &cache.Nop{},
|
||||
WebRoot: "/tmp",
|
||||
@@ -217,8 +216,18 @@ func prep(t *testing.T) (srv *Rest, ts *httptest.Server) {
|
||||
|
||||
err = ioutil.WriteFile(testHTML, []byte("some html"), 0700)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ts = httptest.NewServer(srv.routes())
|
||||
return srv, ts
|
||||
|
||||
teardown = func() {
|
||||
ts.Close()
|
||||
srv.DataService.Close()
|
||||
os.Remove(testDb)
|
||||
os.Remove(testHTML)
|
||||
os.RemoveAll("/tmp/ava-remark42")
|
||||
}
|
||||
|
||||
return ts, srv, teardown
|
||||
}
|
||||
|
||||
func get(t *testing.T, url string) (string, int) {
|
||||
@@ -230,11 +239,24 @@ func get(t *testing.T, url string) (string, int) {
|
||||
return string(body), r.StatusCode
|
||||
}
|
||||
|
||||
func getWithAuth(t *testing.T, url string) (string, int) {
|
||||
func getWithDevAuth(t *testing.T, url string) (body string, code int) {
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
r, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
defer r.Body.Close()
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
assert.Nil(t, err)
|
||||
return string(b), r.StatusCode
|
||||
}
|
||||
|
||||
func getWithAdminAuth(t *testing.T, url string) (string, int) {
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("admin", "password")
|
||||
r, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
defer r.Body.Close()
|
||||
@@ -242,24 +264,22 @@ func getWithAuth(t *testing.T, url string) (string, int) {
|
||||
assert.Nil(t, err)
|
||||
return string(body), r.StatusCode
|
||||
}
|
||||
|
||||
func post(t *testing.T, url string, body string) (*http.Response, error) {
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(body))
|
||||
assert.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.SetBasicAuth("admin", "password")
|
||||
return client.Do(req)
|
||||
}
|
||||
|
||||
func addComment(t *testing.T, c store.Comment, ts *httptest.Server) string {
|
||||
|
||||
b, err := json.Marshal(c)
|
||||
require.Nil(t, err, "can't marshal comment %+v", c)
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest("POST", ts.URL+"/api/v1/comment", bytes.NewBuffer(b))
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("dev", "password")
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err := client.Do(req)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, http.StatusCreated, resp.StatusCode)
|
||||
|
||||
@@ -12,9 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func TestServer_RssPost(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
waitOnSecChange()
|
||||
|
||||
@@ -53,9 +52,8 @@ func TestServer_RssPost(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RssSite(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
waitOnSecChange()
|
||||
|
||||
@@ -107,9 +105,8 @@ func TestServer_RssSite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RssWithReply(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, _, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
waitOnSecChange()
|
||||
|
||||
@@ -159,9 +156,8 @@ func TestServer_RssWithReply(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_RssReplies(t *testing.T) {
|
||||
srv, ts := prep(t)
|
||||
assert.NotNil(t, srv)
|
||||
defer cleanup(ts, srv)
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
waitOnSecChange()
|
||||
|
||||
|
||||
+1
-1
@@ -19,5 +19,5 @@ script:
|
||||
- GO111MODULE=on go get ./...
|
||||
- GO111MODULE=on go mod vendor
|
||||
- GO111MODULE=on go test -v -mod=vendor -covermode=count -coverprofile=profile.cov ./... || travis_terminate 1;
|
||||
- ./bin/gometalinter --deadline=120s --exclude=test --exclude=mock --exclude=vendor --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=megacheck --enable=ineffassign --enable=varcheck --enable=unconvert --enable=deadcode --enable=interfacer --enable=gotype ./... || travis_terminate 1;
|
||||
- ./bin/gometalinter --deadline=120s --exclude=test --exclude=mock --exclude=vendor --exclude=_example --disable-all --enable=errcheck --enable=vet --enable=vetshadow --enable=megacheck --enable=ineffassign --enable=varcheck --enable=unconvert --enable=deadcode --enable=interfacer --enable=gotype ./... || travis_terminate 1;
|
||||
- $GOPATH/bin/goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
|
||||
+125
-22
@@ -1,17 +1,20 @@
|
||||
# auth - authentication via oauth2 [](https://travis-ci.org/go-pkgz/auth) [](https://coveralls.io/github/go-pkgz/auth?branch=master)
|
||||
# auth - authentication via oauth2 [](https://travis-ci.org/go-pkgz/auth) [](https://coveralls.io/github/go-pkgz/auth?branch=master) [](https://godoc.org/github.com/go-pkgz/auth)
|
||||
|
||||
|
||||
|
||||
This library provides "social login" with Github, Google, Facebook and Yandex.
|
||||
|
||||
- Multiple oauth2 providers can be used at the same time
|
||||
- Special `dev` provider allows local testing and development
|
||||
- JWT stored in a secure cookie and with XSRF protection. Cookies can be session-only
|
||||
- JWT stored in a secure cookie with XSRF protection. Cookies can be session-only
|
||||
- Minimal scopes with user name, id and picture (avatar) only
|
||||
- Integrated avatar proxy with FS, boltdb or gridfs storage
|
||||
- Support of user-defined storages
|
||||
- Integrated avatar proxy with FS, boltdb and gridfs storages
|
||||
- Support of user-defined storages for avatars
|
||||
- Black list with user-defined validator
|
||||
- Multiple aud (audience) supported
|
||||
- Secure key with customizable `SecretReader`
|
||||
- Ability to store extra information to token and retrieve on login
|
||||
- Pre-auth and post-auth hooks to handle custom use cases.
|
||||
- Middleware for easy integration into http routers
|
||||
|
||||
## Install
|
||||
@@ -23,25 +26,26 @@ This library provides "social login" with Github, Google, Facebook and Yandex.
|
||||
Example with chi router:
|
||||
|
||||
```go
|
||||
|
||||
func main() {
|
||||
/// define options
|
||||
options := auth.Opts{
|
||||
SecretReader: token.SecretFunc(func(id string) (string, error) { return "secret", nil }), // secret key for JWT
|
||||
SecretReader: token.SecretFunc(func(id string) (string, error) { // secret key for JWT
|
||||
return "secret", nil
|
||||
}),
|
||||
TokenDuration: time.Hour,
|
||||
CookieDuration: time.Hour * 24,
|
||||
Issuer: "my-test-app",
|
||||
URL: "http://127.0.0.1:8080",
|
||||
AvatarStore: avatar.NewLocalFS("/tmp", 120),
|
||||
Validator: middleware.ValidatorFunc(func(_ string, claims token.Claims) bool {
|
||||
return claims.User != nil && strings.HasPrefix(claims.User.Name, "dev_") // allow only dev_ names
|
||||
}),
|
||||
AvatarStore: avatar.NewLocalFS("/tmp"),
|
||||
Validator: token.ValidatorFunc(func(_ string, claims token.Claims) bool {
|
||||
// allow only dev_* names
|
||||
return claims.User != nil && strings.HasPrefix(claims.User.Name, "dev_")
|
||||
}),
|
||||
}
|
||||
|
||||
// create auth service
|
||||
service, err := auth.NewService(options)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// create auth service with providers
|
||||
service := auth.NewService(options)
|
||||
service.AddProvider("github", "<Client ID>", "<Client Secret>") // add github provider
|
||||
service.AddProvider("facebook", "<Client ID>", "<Client Secret>") // add facebook provider
|
||||
|
||||
@@ -69,7 +73,104 @@ func main() {
|
||||
- `middleware.Auth` - requires authenticated user
|
||||
- `middleware.Admin` - requires authenticated and admin user
|
||||
- `middleware.Trace` - doesn't require authenticated user, but adds user info to request
|
||||
|
||||
|
||||
## Details
|
||||
|
||||
Generally, adding support of `auth` includes a few relatively simple steps:
|
||||
|
||||
1. Setup `auth.Opts` structure with all parameters. Each of them [documented](https://github.com/go-pkgz/auth/blob/master/auth.go#L29) and most of parameters are optional and have sane defaults.
|
||||
2. [Create](https://github.com/go-pkgz/auth/blob/master/auth.go#L56) the new `auth.Service` with provided options.
|
||||
3. [Add all](https://github.com/go-pkgz/auth/blob/master/auth.go#L149) desirable authentication providers. Currently supported Github, Google, Facebook and Yandex
|
||||
4. Retrieve [middleware](https://github.com/go-pkgz/auth/blob/master/auth.go#L144) and [http handlers](https://github.com/go-pkgz/auth/blob/master/auth.go#L105) from `auth.Service`
|
||||
5. Wire auth and avatar handlers into http router as sub–routes.
|
||||
|
||||
### API
|
||||
|
||||
For the example above authentication handlers wired as `/auth` and provides:
|
||||
|
||||
- `/auth/<provider>/login?id=<site_id>&from=<redirect_url>` - site_id used as `aud` claim for the token and can be processed by `SecretReader` to load/retrieve/define different secrets. redirect_url is the url to redirect after successful login.
|
||||
- `/avatar/<avatar_id>` - returns the avatar (image). Links to those pictures added into user info automatically, for details see "Avatar proxy"
|
||||
- `/auth/<provider>/logout` and `/auth/logout` - invalidate "session" by removing JWT cookie
|
||||
- `/auth/list` - gives a json list of active providers
|
||||
- `/auth/user` - returns `token.User` (json)
|
||||
|
||||
### User info
|
||||
|
||||
Middleware populates `token.User` to request's context. It can be loaded with `token.GetUserInfo(r *http.Request) (user User, err error)` or `token.MustGetUserInfo(r *http.Request) User` functions.
|
||||
|
||||
`token.User` object includes all fields retrieved from oauth2 provider:
|
||||
- `Name` - user name
|
||||
- `ID` - hash of user id
|
||||
- `Picture` - full link to proxied avatar (see "Avatar proxy")
|
||||
|
||||
It also has placeholders for fields application can populate with custom `token.ClaimsUpdater` (see "Customization")
|
||||
|
||||
- `IP` - hash of user's IP address
|
||||
- `Email` - user's email
|
||||
- `Attributes` - map of string:any-value. To simplify management of this map some setters and getters provides, for example `users.StrAttr`, `user.SetBoolAttr` and so on. See [user.go](https://github.com/go-pkgz/auth/blob/master/token/user.go) for more details.
|
||||
|
||||
|
||||
### Avatar proxy
|
||||
|
||||
Direct links to avatars won't survive any real-life usage if they linked from a public page. For example, page [like this](https://remark42.com/demo/) may have hundreds of avatars and, most likely, will trigger throttling on provider's side. To eliminate such restriction `auth` library provides and automatic proxy
|
||||
|
||||
- On each login the proxy will retrieve user's picture and save it to `AvatarStore`
|
||||
- Local (proxied) link to avatar included in user's info (jwt token)
|
||||
- API for avatar removal provided as a part of `AvatarStore`
|
||||
- User can leverage one of provided stores:
|
||||
- `avatar.LocalFS` - file system, each avatar in a separate file
|
||||
- `avatar.BoltDB` - a single [boltdb](https://github.com/coreos/bbolt) file (embedded KV store).
|
||||
- `avatar.GridFS` - external [GridFS](https://docs.mongodb.com/manual/core/gridfs/) (mongo db).
|
||||
- In case of need a custom implementation of other stores can be passed in and used by `auth` library. Each store has to implement `avatar.Store` [interface](https://github.com/go-pkgz/auth/blob/master/avatar/store.go#L25).
|
||||
- All avatar-related setup done as a part of `auth.Opts` and needs:
|
||||
- `AvatarStore` - avatar store to use, i.e. `avatar.NewLocalFS("/tmp/avatars")`
|
||||
- `AvatarRoutePath` - route prefix for direct links to proxied avatar. For example `/api/v1/avatars` will make full links links this - `http://example.com/api/v1/avatars/1234567890123.image`. The url will be stored in user's token and retrieved by middleware (see "User Info")
|
||||
- `AvatarResizeLimit` - size (in pixel) used to resize avatar. Pls note - resize happens once as a part of `Put` call, i.e. on login. 0 size (default) disables resizing.
|
||||
|
||||
### Customization
|
||||
|
||||
There are several ways to adjust functionality of the library:
|
||||
|
||||
1. `SecretReader` - interface with a single method `Get(aud string) string` to return secret used for JWT signing and verification
|
||||
1. `ClaimsUpdater` - interface with `Update(claims Claims) Claims` method. This is the primary way to alter a token at login time and add any attributes, set ip, email, admin status and so on.
|
||||
2. `Validator` - interface with `Validate(token string, claims Claims) bool` method. This is post-token hook and will be called on **each request** wrapped with `Auth` middleware. This will be the place for special logic to reject some tokens or users.
|
||||
|
||||
All of interfaces have corresponding Func wrappers (adapters) - `SecretFunc`, `ClaimsUpdFunc` and `ValidatorFunc`.
|
||||
|
||||
### Implementing black list logic or some other filters
|
||||
|
||||
Restricting some users or some tokens is two step process:
|
||||
|
||||
- `ClaimsUpdater` sets an attribute, like `blocked` (or `allowed`)
|
||||
- `Validator` checks the attribute and returns true/false
|
||||
|
||||
_This technic used in the [example](https://github.com/go-pkgz/auth/blob/master/_example/backend/main.go#L36) code_
|
||||
|
||||
The process can be simplified by doing all checks directly in `Validator`, but depends on particular case such solution
|
||||
can be too expensive because `Validator` runs on each request as a part of auth middleware. In contrast, `ClaimsUpdater` called on token creation/refresh only.
|
||||
|
||||
|
||||
### Dev provider
|
||||
|
||||
Working with oauth2 providers can be a pain, especially during development phase. A special, development-only provider `dev` can make it less painful. This one can be registered directly, i.e. `service.AddProvider("dev", "", "")` and should be activated like this:
|
||||
|
||||
```go
|
||||
// runs dev oauth2 server on :8084
|
||||
go func() {
|
||||
p, err := service.Provider("dev")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
devAuthServer := provider.DevAuthServer{Provider: p}
|
||||
devAuthServer.Run()
|
||||
}()
|
||||
```
|
||||
|
||||
It will run fake aouth2 "server" on port :8084 and user could login with any user name. See [example](https://github.com/go-pkgz/auth/blob/master/_example/backend/main.go) for more details.
|
||||
|
||||
_Warning: this is not the real oauth2 server but just a small fake thing for development and testing only. Don't use `dev` provider with any production code._
|
||||
|
||||
|
||||
## Register oauth2 providers
|
||||
|
||||
Authentication handled by external providers. You should setup oauth2 for all (or some) of them to allow users to authenticate. It is not mandatory to have all of them, but at least one should be correctly configured.
|
||||
@@ -77,11 +178,11 @@ Authentication handled by external providers. You should setup oauth2 for all (o
|
||||
#### Google Auth Provider
|
||||
|
||||
1. Create a new project: https://console.developers.google.com/project
|
||||
1. Choose the new project from the top right project dropdown (only if another project is selected)
|
||||
1. In the project Dashboard center pane, choose **"API Manager"**
|
||||
1. In the left Nav pane, choose **"Credentials"**
|
||||
1. In the center pane, choose **"OAuth consent screen"** tab. Fill in **"Product name shown to users"** and hit save.
|
||||
1. In the center pane, choose **"Credentials"** tab.
|
||||
2. Choose the new project from the top right project dropdown (only if another project is selected)
|
||||
3. In the project Dashboard center pane, choose **"API Manager"**
|
||||
4. In the left Nav pane, choose **"Credentials"**
|
||||
5. In the center pane, choose **"OAuth consent screen"** tab. Fill in **"Product name shown to users"** and hit save.
|
||||
6. In the center pane, choose **"Credentials"** tab.
|
||||
* Open the **"New credentials"** drop down
|
||||
* Choose **"OAuth client ID"**
|
||||
* Choose **"Web application"**
|
||||
@@ -89,7 +190,7 @@ Authentication handled by external providers. You should setup oauth2 for all (o
|
||||
* Authorized origins is your domain ex: `https://example.mysite.com`
|
||||
* Authorized redirect URIs is the location of oauth2/callback constructed as domain + `/auth/google/callback`, ex: `https://example.mysite.com/auth/google/callback`
|
||||
* Choose **"Create"**
|
||||
2. Take note of the **Client ID** and **Client Secret**
|
||||
7. Take note of the **Client ID** and **Client Secret**
|
||||
|
||||
_instructions for google oauth2 setup borrowed from [oauth2_proxy](https://github.com/bitly/oauth2_proxy)_
|
||||
|
||||
@@ -125,4 +226,6 @@ For more details refer to [Yandex OAuth](https://tech.yandex.com/oauth/doc/dg/co
|
||||
|
||||
## Status
|
||||
|
||||
The library extracted from [remark42](https://github.com/umputun/remark) project. The code in production use on multiple sites and seems to work fine.
|
||||
The library extracted from [remark42](https://github.com/umputun/remark) project. The original code in production use on multiple sites and seems to work fine.
|
||||
|
||||
`go-pkgz/auth` library still in beta and until version 1 released some breaking changes still possible.
|
||||
+29
-13
@@ -27,7 +27,7 @@ type Service struct {
|
||||
|
||||
// Opts is a full set of all parameters to initialize Service
|
||||
type Opts struct {
|
||||
SecretReader token.Secret // reader returns secret for given site id (aud)
|
||||
SecretReader token.Secret // reader returns secret for given site id (aud), required
|
||||
ClaimsUpd token.ClaimsUpdater // updater for jwt to add/modify values stored in the token
|
||||
SecureCookies bool // makes jwt cookie secure
|
||||
TokenDuration time.Duration // token's TTL, refreshed automatically
|
||||
@@ -42,14 +42,14 @@ type Opts struct {
|
||||
|
||||
Issuer string // optional value for iss claim, usually the application name, default "go-pkgz/auth"
|
||||
|
||||
URL string // root url for the rest service, i.e. http://blah.example.com
|
||||
URL string // root url for the rest service, i.e. http://blah.example.com, required
|
||||
Validator token.Validator // validator allows to reject some valid tokens with user-defined logic
|
||||
|
||||
AvatarStore avatar.Store // store to save/load avatars
|
||||
AvatarStore avatar.Store // store to save/load avatars, required
|
||||
AvatarResizeLimit int // resize avatar's limit in pixels
|
||||
AvatarRoutePath string // avatar routing prefix, i.e. "/api/v1/avatar"
|
||||
AvatarRoutePath string // avatar routing prefix, i.e. "/api/v1/avatar", default `/avatar`
|
||||
|
||||
DevPasswd string // if presented, allows basic auth with user dev and given password
|
||||
AdminPasswd string // if presented, allows basic auth with user admin and given password
|
||||
}
|
||||
|
||||
// NewService initializes everything
|
||||
@@ -71,7 +71,7 @@ func NewService(opts Opts) *Service {
|
||||
|
||||
if opts.SecretReader == nil {
|
||||
jwtService.SecretReader = token.SecretFunc(func(id string) (string, error) {
|
||||
return "", errors.New("secrets reader not avalibale")
|
||||
return "", errors.New("secrets reader not available")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ func NewService(opts Opts) *Service {
|
||||
opts: opts,
|
||||
jwtService: jwtService,
|
||||
authMiddleware: middleware.Authenticator{
|
||||
JWTService: jwtService,
|
||||
Validator: opts.Validator,
|
||||
DevPasswd: opts.DevPasswd,
|
||||
JWTService: jwtService,
|
||||
Validator: opts.Validator,
|
||||
AdminPasswd: opts.AdminPasswd,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -96,6 +96,9 @@ func NewService(opts Opts) *Service {
|
||||
RoutePath: opts.AvatarRoutePath,
|
||||
ResizeLimit: opts.AvatarResizeLimit,
|
||||
}
|
||||
if res.avatarProxy.RoutePath == "" {
|
||||
res.avatarProxy.RoutePath = "/avatar"
|
||||
}
|
||||
}
|
||||
|
||||
return &res
|
||||
@@ -104,7 +107,7 @@ func NewService(opts Opts) *Service {
|
||||
// Handlers gets http.Handler for all providers and avatars
|
||||
func (s *Service) Handlers() (authHandler http.Handler, avatarHandler http.Handler) {
|
||||
|
||||
providerHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
ah := func(w http.ResponseWriter, r *http.Request) {
|
||||
elems := strings.Split(r.URL.Path, "/")
|
||||
if len(elems) < 2 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
@@ -127,6 +130,19 @@ func (s *Service) Handlers() (authHandler http.Handler, avatarHandler http.Handl
|
||||
return
|
||||
}
|
||||
|
||||
// show user info
|
||||
if elems[len(elems)-1] == "user" {
|
||||
claims, _, err := s.jwtService.Get(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
rest.RenderJSON(w, r, rest.JSON{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
rest.RenderJSON(w, r, claims.User)
|
||||
return
|
||||
}
|
||||
|
||||
// regular auth handlers
|
||||
provName := elems[len(elems)-2]
|
||||
p, err := s.Provider(provName)
|
||||
if err != nil {
|
||||
@@ -137,10 +153,10 @@ func (s *Service) Handlers() (authHandler http.Handler, avatarHandler http.Handl
|
||||
p.Handler(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(providerHandler), http.HandlerFunc(s.avatarProxy.Handler)
|
||||
return http.HandlerFunc(ah), http.HandlerFunc(s.avatarProxy.Handler)
|
||||
}
|
||||
|
||||
// Middleware returns token middleware
|
||||
// Middleware returns auth middleware
|
||||
func (s *Service) Middleware() middleware.Authenticator {
|
||||
return s.authMiddleware
|
||||
}
|
||||
@@ -152,7 +168,7 @@ func (s *Service) AddProvider(name string, cid string, csecret string) {
|
||||
URL: s.opts.URL,
|
||||
JwtService: s.jwtService,
|
||||
Issuer: s.issuer,
|
||||
AvatarProxy: s.avatarProxy,
|
||||
AvatarSaver: s.avatarProxy,
|
||||
Cid: cid,
|
||||
Csecret: csecret,
|
||||
}
|
||||
|
||||
+1
-9
@@ -2,22 +2,14 @@ module github.com/go-pkgz/auth
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.34.0 // indirect
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/coreos/bbolt v1.3.0
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/go-pkgz/mongo v1.0.0
|
||||
github.com/go-pkgz/rest v1.1.1
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/go-pkgz/rest v1.1.5
|
||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 // indirect
|
||||
google.golang.org/appengine v1.4.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
|
||||
+6
-23
@@ -1,7 +1,5 @@
|
||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||
github.com/coreos/bbolt v1.3.0 h1:HIgH5xUWXT914HCI671AxuTTqjj64UOFr7pHn48LUTI=
|
||||
github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -10,20 +8,15 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
|
||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-pkgz/mongo v1.0.0 h1:9jijAK7prCRMetiyTu3c1rv/2lMypzuf2DWcVpTlwzw=
|
||||
github.com/go-pkgz/mongo v1.0.0/go.mod h1:R9si/F2aJsjz4MUxhzuppIHY8yLV3YCeuCpgcI50cu4=
|
||||
github.com/go-pkgz/rest v1.1.1 h1:YuLe+wOJwcE+Y0SkJ+AtvUOPGjTMe4Q4vg98Uqs9CKc=
|
||||
github.com/go-pkgz/rest v1.1.1/go.mod h1:DIxxm3vSt6e+IY+UQUOFsfB2YaHLmGoOfPLWN5pxQSA=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/go-pkgz/rest v1.1.3 h1:rMf+xJn8i1Ip9OKohusZsRxwntM0BwYu8OX8BuEwN80=
|
||||
github.com/go-pkgz/rest v1.1.3/go.mod h1:DIxxm3vSt6e+IY+UQUOFsfB2YaHLmGoOfPLWN5pxQSA=
|
||||
github.com/go-pkgz/rest v1.1.4 h1:/Lrg9kBWBjNah7nmCDHLszRAfVVBIy5ajf0vVgpHPi0=
|
||||
github.com/go-pkgz/rest v1.1.4/go.mod h1:DIxxm3vSt6e+IY+UQUOFsfB2YaHLmGoOfPLWN5pxQSA=
|
||||
github.com/go-pkgz/rest v1.1.5 h1:5br4mnscfLb27yxv5hJFLBVmAt09PrmIBP+meA3CfHc=
|
||||
github.com/go-pkgz/rest v1.1.5/go.mod h1:DIxxm3vSt6e+IY+UQUOFsfB2YaHLmGoOfPLWN5pxQSA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 h1:Ys0rDzh8s4UMlGaDa1UTA0sfKgvF0hQZzTYX8ktjiDc=
|
||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022/go.mod h1:x4NsS+uc7ecH/Cbm9xKQ6XzmJM57rWTkjywjfB2yQ18=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
@@ -34,17 +27,7 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b h1:VHyIDlv3XkfCa5/a81uzaoDkHH4rr81Z62g+xlnO8uM=
|
||||
golang.org/x/image v0.0.0-20181116024801-cd38e8056d9b/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcpLYqYK8wHcdCetU3VuMBJE=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6 h1:IcgEB62HYgAhX0Nd/QrVgZlxlcyxbGQHElLUhW2X4Fo=
|
||||
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
+23
-54
@@ -13,20 +13,12 @@ import (
|
||||
"github.com/go-pkgz/auth/token"
|
||||
)
|
||||
|
||||
// Authenticator is top level token object providing middlewares
|
||||
// Authenticator is top level auth object providing middlewares
|
||||
type Authenticator struct {
|
||||
JWTService *token.Service
|
||||
Providers []provider.Service
|
||||
Validator token.Validator
|
||||
DevPasswd string
|
||||
}
|
||||
|
||||
var devUser = token.User{
|
||||
ID: "dev",
|
||||
Name: "developer one",
|
||||
Attributes: map[string]interface{}{
|
||||
"admin": true,
|
||||
},
|
||||
JWTService *token.Service
|
||||
Providers []provider.Service
|
||||
Validator token.Validator
|
||||
AdminPasswd string
|
||||
}
|
||||
|
||||
var adminUser = token.User{
|
||||
@@ -37,7 +29,7 @@ var adminUser = token.User{
|
||||
},
|
||||
}
|
||||
|
||||
// Auth middleware adds token from session and populates user info
|
||||
// Auth middleware adds auth from session and populates user info
|
||||
func (a *Authenticator) Auth(next http.Handler) http.Handler {
|
||||
return a.auth(true)(next)
|
||||
}
|
||||
@@ -47,6 +39,7 @@ func (a *Authenticator) Trace(next http.Handler) http.Handler {
|
||||
return a.auth(false)(next)
|
||||
}
|
||||
|
||||
// auth implements all logic for authentication (reqAuth=true) and tracing (reqAuth=false)
|
||||
func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
|
||||
onError := func(h http.Handler, w http.ResponseWriter, r *http.Request, err error) {
|
||||
@@ -57,27 +50,20 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
log.Printf("[DEBUG] failed token, %s", err)
|
||||
log.Printf("[DEBUG] auth failed, %s", err)
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
f := func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// if secret key matches for given site (from request) return admin user
|
||||
if a.checkSecretKey(r) {
|
||||
// use admin user basic auth if enabled
|
||||
if a.basicAdminUser(r) {
|
||||
r = token.SetUserInfo(r, adminUser)
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// use dev user basic token if enabled
|
||||
if a.basicDevUser(r) {
|
||||
r = token.SetUserInfo(r, devUser)
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
claims, tkn, err := a.JWTService.Get(r)
|
||||
if err != nil {
|
||||
onError(h, w, r, errors.Wrap(err, "can't get token"))
|
||||
@@ -85,12 +71,12 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
if claims.Handshake != nil { // handshake in token indicate special use cases, not for login
|
||||
onError(h, w, r, errors.Errorf("invalid kind of token for %s/%s", claims.User.Name, claims.User.ID))
|
||||
onError(h, w, r, errors.New("invalid kind of token"))
|
||||
return
|
||||
}
|
||||
|
||||
if claims.User == nil {
|
||||
onError(h, w, r, errors.New("failed token, no user info presented in the claim"))
|
||||
onError(h, w, r, errors.New("failed auth, no user info presented in the claim"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -121,29 +107,11 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
return f
|
||||
}
|
||||
|
||||
func (a *Authenticator) checkSecretKey(r *http.Request) bool {
|
||||
if a.JWTService.SecretReader == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
aud := r.URL.Query().Get("aud")
|
||||
secret := r.URL.Query().Get("secret")
|
||||
|
||||
skey, err := a.JWTService.SecretReader.Get(aud)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.TrimSpace(secret) == "" || secret != skey {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// refreshExpiredToken makes new token with passed claims, but only if permission allowed
|
||||
// refreshExpiredToken makes a new token with passed claims
|
||||
func (a *Authenticator) refreshExpiredToken(w http.ResponseWriter, claims token.Claims) (token.Claims, error) {
|
||||
// refresh token
|
||||
if err := a.JWTService.Set(w, claims, false); err != nil {
|
||||
|
||||
claims.ExpiresAt = 0 // this will cause now+duration for refreshed token
|
||||
if err := a.JWTService.Set(w, claims); err != nil {
|
||||
return token.Claims{}, err
|
||||
}
|
||||
return claims, nil
|
||||
@@ -168,9 +136,10 @@ func (a *Authenticator) AdminOnly(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func (a *Authenticator) basicDevUser(r *http.Request) bool {
|
||||
// basic auth for admin user
|
||||
func (a *Authenticator) basicAdminUser(r *http.Request) bool {
|
||||
|
||||
if a.DevPasswd == "" {
|
||||
if a.AdminPasswd == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -181,18 +150,18 @@ func (a *Authenticator) basicDevUser(r *http.Request) bool {
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
log.Printf("[WARN] dev user token failed, failed to decode %s, %s", s[1], err)
|
||||
log.Printf("[WARN] admin user auth failed, can't to decode %s, %s", s[1], err)
|
||||
return false
|
||||
}
|
||||
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
log.Printf("[WARN] dev user token failed, failed to split %s", string(b))
|
||||
log.Printf("[WARN] admin user auth failed, can't split basic auth %s", string(b))
|
||||
return false
|
||||
}
|
||||
|
||||
if pair[0] != "dev" || pair[1] != a.DevPasswd {
|
||||
log.Printf("[WARN] dev user token failed, user/passwd mismatch %+v", pair)
|
||||
if pair[0] != "admin" || pair[1] != a.AdminPasswd {
|
||||
log.Printf("[WARN] dev user auth failed, user/passwd mismatch %+v", pair)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
+124
-17
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/nullrocks/identicon"
|
||||
@@ -46,6 +47,12 @@ func (d *DevAuthServer) Run() {
|
||||
log.Printf("[WARN] can't create identicon, %s", err)
|
||||
}
|
||||
|
||||
userFormTmpl, err := template.New("page").Parse(devUserFormTmpl)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] can't parse user form template, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
d.httpServer = &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", devAuthPort),
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -56,7 +63,10 @@ func (d *DevAuthServer) Run() {
|
||||
|
||||
// first time it will be called without username and will ask for one
|
||||
if !d.Automatic && (r.ParseForm() != nil || r.Form.Get("username") == "") {
|
||||
if _, err = w.Write([]byte(fmt.Sprintf(devUserForm, r.URL.RawQuery))); err != nil {
|
||||
|
||||
formData := struct{ Query string }{Query: r.URL.RawQuery}
|
||||
|
||||
if err = userFormTmpl.Execute(w, formData); err != nil {
|
||||
log.Printf("[WARN] can't write, %s", err)
|
||||
}
|
||||
return
|
||||
@@ -176,24 +186,121 @@ func (d *DevAuthServer) genAvatar(user string) ([]byte, error) {
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
var devUserForm = `
|
||||
var devUserFormTmpl = `
|
||||
<html>
|
||||
<head>
|
||||
<title>Dev User</title>
|
||||
<style>
|
||||
form {
|
||||
margin: 100 auto;
|
||||
width: 300px;
|
||||
padding: 1em;
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<head>
|
||||
<title>Dev OAuth</title>
|
||||
<style>
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: hsl(200, 50%, 50%);
|
||||
text-decoration-color: hsla(200, 50%, 50%, 0.5);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: hsl(200, 50%, 70%);
|
||||
text-decoration-color: hsla(200, 50%, 70%, 0.5);
|
||||
}
|
||||
|
||||
form {
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
margin: 100px auto;
|
||||
display: inline-block;
|
||||
padding: 1em;
|
||||
box-shadow: 0 0 0.1rem rgba(0, 0, 0, 0.2), 0 0 0.4rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-header h1 a:not(:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-header p {
|
||||
opacity: 0.6;
|
||||
margin-top: 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.username-label {
|
||||
opacity: 0.6;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.username-input {
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.form-submit {
|
||||
border: none;
|
||||
background: hsl(200, 50%, 50%);
|
||||
color: white;
|
||||
font: inherit;
|
||||
padding: 0.4em 0.8em 0.3em 0.8em;
|
||||
border-radius: 0.2em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-submit:hover,
|
||||
.form-submit:focus {
|
||||
background-color: hsl(200, 50%, 70%);
|
||||
}
|
||||
|
||||
.form-submit:active {
|
||||
background-color: hsl(200, 80%, 70%);
|
||||
}
|
||||
|
||||
.username-label,
|
||||
.username-input,
|
||||
.form-submit {
|
||||
display: block;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin: 0;
|
||||
margin-top: 2rem;
|
||||
font-size: 0.8em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form action="/login/oauth/authorize?%s" method="post">
|
||||
username: <input type="text" name="username" value="dev_user">
|
||||
<input type="submit" value="Login">
|
||||
<form action="/login/oauth/authorize?{{.Query}}" method="post">
|
||||
<header class="form-header">
|
||||
<h1><a href="https://github.com/go-pkgz/auth">GO-PKGZ/AUTH</a></h1>
|
||||
<p>Dev Provider</p>
|
||||
</header>
|
||||
<label>
|
||||
<span class="username-label">Username</span>
|
||||
<input
|
||||
class="username-input"
|
||||
type="text"
|
||||
name="username"
|
||||
value="dev_user"
|
||||
autofocus
|
||||
/>
|
||||
</label>
|
||||
<input type="submit" class="form-submit" value="Authorize" />
|
||||
<p class="notice">Not for production use</p>
|
||||
</form>
|
||||
</body>
|
||||
</body>
|
||||
<script>
|
||||
var input = document.querySelector(".username-input");
|
||||
input.focus();
|
||||
input.setSelectionRange(0, input.value.length)
|
||||
</script>
|
||||
</html>
|
||||
`
|
||||
|
||||
+5
-5
@@ -18,8 +18,8 @@ func NewGoogle(p Params) Service {
|
||||
return initService(p, Service{
|
||||
Name: "google",
|
||||
Endpoint: google.Endpoint,
|
||||
RedirectURL: p.URL + "/token/google/callback",
|
||||
Scopes: []string{"https://www.googleapis.com/token/userinfo.profile"},
|
||||
RedirectURL: p.URL + "/auth/google/callback",
|
||||
Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile"},
|
||||
InfoURL: "https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
MapUser: func(data userData, _ []byte) token.User {
|
||||
userInfo := token.User{
|
||||
@@ -41,7 +41,7 @@ func NewGithub(p Params) Service {
|
||||
return initService(p, Service{
|
||||
Name: "github",
|
||||
Endpoint: github.Endpoint,
|
||||
RedirectURL: p.URL + "/token/github/callback",
|
||||
RedirectURL: p.URL + "/auth/github/callback",
|
||||
Scopes: []string{},
|
||||
InfoURL: "https://api.github.com/user",
|
||||
MapUser: func(data userData, _ []byte) token.User {
|
||||
@@ -76,7 +76,7 @@ func NewFacebook(p Params) Service {
|
||||
return initService(p, Service{
|
||||
Name: "facebook",
|
||||
Endpoint: facebook.Endpoint,
|
||||
RedirectURL: p.URL + "/token/facebook/callback",
|
||||
RedirectURL: p.URL + "/auth/facebook/callback",
|
||||
Scopes: []string{"public_profile"},
|
||||
InfoURL: "https://graph.facebook.com/me?fields=id,name,picture",
|
||||
MapUser: func(data userData, bdata []byte) token.User {
|
||||
@@ -102,7 +102,7 @@ func NewYandex(p Params) Service {
|
||||
return initService(p, Service{
|
||||
Name: "yandex",
|
||||
Endpoint: yandex.Endpoint,
|
||||
RedirectURL: p.URL + "/token/yandex/callback",
|
||||
RedirectURL: p.URL + "/auth/yandex/callback",
|
||||
Scopes: []string{},
|
||||
// See https://tech.yandex.com/passport/doc/dg/reference/response-docpage/
|
||||
InfoURL: "https://login.yandex.ru/info?format=json",
|
||||
|
||||
+21
-13
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/go-pkgz/auth/avatar"
|
||||
"github.com/go-pkgz/auth/token"
|
||||
)
|
||||
|
||||
@@ -37,12 +36,17 @@ type Service struct {
|
||||
type Params struct {
|
||||
URL string
|
||||
JwtService *token.Service
|
||||
AvatarProxy *avatar.Proxy
|
||||
AvatarSaver AvatarSaver
|
||||
Cid string
|
||||
Csecret string
|
||||
Issuer string
|
||||
}
|
||||
|
||||
// AvatarSaver defines minimal interface to save avatar
|
||||
type AvatarSaver interface {
|
||||
Put(u token.User) (avatarURL string, err error)
|
||||
}
|
||||
|
||||
type userData map[string]interface{}
|
||||
|
||||
func (u userData) value(key string) string {
|
||||
@@ -53,9 +57,9 @@ func (u userData) value(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// initService makes token service for given provider
|
||||
// initService makes oauth2 service for given provider
|
||||
func initService(p Params, service Service) Service {
|
||||
log.Printf("[INFO] init token service %s", service.Name)
|
||||
log.Printf("[INFO] init oauth2 service %s", service.Name)
|
||||
service.Params = p
|
||||
service.conf = oauth2.Config{
|
||||
ClientID: service.Cid,
|
||||
@@ -65,7 +69,7 @@ func initService(p Params, service Service) Service {
|
||||
Endpoint: service.Endpoint,
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] created %s token, id=%s, redir=%s, endpoint=%s",
|
||||
log.Printf("[DEBUG] created %s oauth2, id=%s, redir=%s, endpoint=%s",
|
||||
service.Name, service.Cid, service.Endpoint, service.RedirectURL)
|
||||
return service
|
||||
}
|
||||
@@ -123,7 +127,7 @@ func (p Service) loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
}
|
||||
|
||||
if err := p.JwtService.Set(w, claims, false); err != nil {
|
||||
if err := p.JwtService.Set(w, claims); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "failed to set token")
|
||||
return
|
||||
}
|
||||
@@ -144,9 +148,14 @@ func (p Service) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if oauthClaims.Handshake == nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusForbidden, nil, "finvalid handshake token")
|
||||
return
|
||||
}
|
||||
|
||||
retrievedState := oauthClaims.Handshake.State
|
||||
if retrievedState == "" || retrievedState != r.URL.Query().Get("state") {
|
||||
http.Error(w, fmt.Sprintf("unexpected state %v", retrievedState), http.StatusUnauthorized)
|
||||
rest.SendErrorJSON(w, r, http.StatusForbidden, nil, "unexpected state")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -160,7 +169,7 @@ func (p Service) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||
client := p.conf.Client(context.Background(), tok)
|
||||
uinfo, err := client.Get(p.InfoURL)
|
||||
if err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, fmt.Sprintf("failed to get client info via %s", p.InfoURL))
|
||||
rest.SendErrorJSON(w, r, http.StatusServiceUnavailable, err, "failed to get client info")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -201,8 +210,8 @@ func (p Service) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||
SessionOnly: oauthClaims.SessionOnly,
|
||||
}
|
||||
|
||||
if err = p.JwtService.Set(w, claims, oauthClaims.SessionOnly); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "failed to save user info")
|
||||
if err = p.JwtService.Set(w, claims); err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "failed to set token")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -218,8 +227,8 @@ func (p Service) authHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// setAvatar saves avatar and puts proxied URL to u.Picture
|
||||
func (p Service) setAvatar(u token.User) token.User {
|
||||
if p.AvatarProxy != nil {
|
||||
if avatarURL, e := p.AvatarProxy.Put(u); e == nil {
|
||||
if p.AvatarSaver != nil {
|
||||
if avatarURL, e := p.AvatarSaver.Put(u); e == nil {
|
||||
u.Picture = avatarURL
|
||||
} else {
|
||||
log.Printf("[WARN] failed to set avatar for %+v, %+v", u, e)
|
||||
@@ -231,7 +240,6 @@ func (p Service) setAvatar(u token.User) token.User {
|
||||
// LogoutHandler - GET /logout
|
||||
func (p Service) LogoutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
p.JwtService.Reset(w)
|
||||
log.Printf("[DEBUG] logout")
|
||||
}
|
||||
|
||||
func (p Service) randToken() (string, error) {
|
||||
|
||||
+2
-2
@@ -154,7 +154,7 @@ func (j *Service) Parse(tokenString string) (Claims, error) {
|
||||
// Set creates token cookie with xsrf cookie and put it to ResponseWriter
|
||||
// accepts claims and sets expiration if none defined. permanent flag means long-living cookie,
|
||||
// false makes it session only.
|
||||
func (j *Service) Set(w http.ResponseWriter, claims Claims, sessionOnly bool) error {
|
||||
func (j *Service) Set(w http.ResponseWriter, claims Claims) error {
|
||||
if claims.ExpiresAt == 0 {
|
||||
claims.ExpiresAt = time.Now().Add(j.TokenDuration).Unix()
|
||||
}
|
||||
@@ -167,7 +167,7 @@ func (j *Service) Set(w http.ResponseWriter, claims Claims, sessionOnly bool) er
|
||||
}
|
||||
|
||||
cookieExpiration := 0 // session cookie
|
||||
if !sessionOnly {
|
||||
if !claims.SessionOnly && claims.Handshake == nil {
|
||||
cookieExpiration = int(j.CookieDuration.Seconds())
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
## REST helpers and middleware [](https://travis-ci.org/go-pkgz/rest) [](https://goreportcard.com/report/github.com/go-pkgz/rest) [](https://coveralls.io/github/go-pkgz/rest?branch=master)
|
||||
## REST helpers and middleware [](https://travis-ci.org/go-pkgz/rest) [](https://goreportcard.com/report/github.com/go-pkgz/rest) [](https://coveralls.io/github/go-pkgz/rest?branch=master) [](https://godoc.org/github.com/go-pkgz/rest)
|
||||
|
||||
|
||||
## Install and update
|
||||
|
||||
|
||||
+10
-6
@@ -1,6 +1,7 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -9,14 +10,14 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SendErrorJSON makes {error: blah, details: blah} json body and responds with error code
|
||||
func SendErrorJSON(w http.ResponseWriter, r *http.Request, code int, err error, details string) {
|
||||
log.Printf("[DEBUG] %s", errDetailsMsg(r, code, err, details))
|
||||
// SendErrorJSON sends {error: msg} with error code and logging error and caller
|
||||
func SendErrorJSON(w http.ResponseWriter, r *http.Request, code int, err error, msg string) {
|
||||
log.Printf("[DEBUG] %s", errDetailsMsg(r, code, err, msg))
|
||||
w.WriteHeader(code)
|
||||
RenderJSON(w, r, map[string]interface{}{"error": err.Error(), "details": details})
|
||||
RenderJSON(w, r, JSON{"error": msg})
|
||||
}
|
||||
|
||||
func errDetailsMsg(r *http.Request, code int, err error, details string) string {
|
||||
func errDetailsMsg(r *http.Request, code int, err error, msg string) string {
|
||||
|
||||
q := r.URL.String()
|
||||
if qun, e := url.QueryUnescape(q); e == nil {
|
||||
@@ -35,5 +36,8 @@ func errDetailsMsg(r *http.Request, code int, err error, details string) string
|
||||
if pos := strings.Index(remoteIP, ":"); pos >= 0 {
|
||||
remoteIP = remoteIP[:pos]
|
||||
}
|
||||
return fmt.Sprintf("%s - %v - %d - %s - %s%s", details, err, code, remoteIP, q, srcFileInfo)
|
||||
if err == nil {
|
||||
err = errors.New("no error")
|
||||
}
|
||||
return fmt.Sprintf("%s - %v - %d - %s - %s%s", msg, err, code, remoteIP, q, srcFileInfo)
|
||||
}
|
||||
|
||||
+7
@@ -36,6 +36,13 @@ const (
|
||||
None
|
||||
)
|
||||
|
||||
// Logger returns default logger middleware
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
l := New(Flags(All), Prefix("[REST]"))
|
||||
return l.Handler(next)
|
||||
|
||||
}
|
||||
|
||||
// New makes rest Logger with given options
|
||||
func New(options ...Option) *Middleware {
|
||||
res := Middleware{
|
||||
|
||||
Reference in New Issue
Block a user