Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support additional authentication methods - Oauth #29

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0ecc411
testing
CubicrootXYZ May 4, 2021
de3f94f
remove testing
CubicrootXYZ May 4, 2021
2c4400e
add PoC for oauth2
CubicrootXYZ May 9, 2021
b79b946
add password anmd user check
CubicrootXYZ May 9, 2021
a247db1
started moving oauth to current authentication
CubicrootXYZ May 11, 2021
7c5ce31
integrate oauth in current auth
CubicrootXYZ May 13, 2021
609eb66
move things again and introduce jwt tokens
CubicrootXYZ May 17, 2021
6ee285d
move to authhandler
CubicrootXYZ May 22, 2021
2dce377
get /auth to work properly
CubicrootXYZ May 23, 2021
e78ffda
stack authentication layers & add documentation
CubicrootXYZ May 23, 2021
f60bb83
add refresh config
CubicrootXYZ May 23, 2021
46027e2
clean up & add file support
CubicrootXYZ May 23, 2021
fc6bc82
move token key to config & clean up
CubicrootXYZ May 30, 2021
393d586
removed replaced code
CubicrootXYZ May 30, 2021
69970e4
Merge branch 'pushbits:master' into oauth-testing
CubicrootXYZ May 30, 2021
cee8001
clean up errors
CubicrootXYZ Jun 2, 2021
0504528
wording & consistency
CubicrootXYZ Jun 3, 2021
364e522
panic when no oauth secrets are set
CubicrootXYZ Jun 3, 2021
a06fe24
clean up & consistency
CubicrootXYZ Jun 3, 2021
1e2114b
register auth handler
CubicrootXYZ Jun 3, 2021
43b2908
move to POST
CubicrootXYZ Jun 3, 2021
48b4f71
added longterm tokens
CubicrootXYZ Jun 5, 2021
5b0c124
use body data for auth
CubicrootXYZ Jun 5, 2021
488f426
reflect changes in readme
CubicrootXYZ Jun 5, 2021
c92b783
let oauth use existing db
CubicrootXYZ Jun 19, 2021
1a1ee00
Merge branch 'master' into oauth-testing
CubicrootXYZ Jul 21, 2021
a5e6472
clean up merge conflicts
CubicrootXYZ Jul 21, 2021
b226a5e
add missing package
CubicrootXYZ Jul 21, 2021
d98aa1c
lowercase errors
CubicrootXYZ Jul 21, 2021
bed622e
allow multiple oauth clients
CubicrootXYZ Sep 12, 2021
4fe8d7f
Merge branch 'master' into oauth-testing
CubicrootXYZ Nov 21, 2021
2c62a49
add ginserver again
CubicrootXYZ Nov 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add PoC for oauth2
added a proof of concept for oauth2
CubicrootXYZ committed May 9, 2021
commit 2c4400eed476587276a1a518bfa3fc616d4c8b1f
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -114,6 +114,36 @@ You can retrieve the token using [pbcli](https://github.com/PushBits/cli) by run
pbcli application show $PB_APPLICATION --url https://pushbits.example.com --username $PB_USERNAME
```

### Authentication

Pushbits offers you two methods of authenticating against the server:

* Basic authentication (`basic`)
* Oauth 2 (`oauth`)

You will find the corresponding setting in the security section.

```yaml
...
security:
...
# The authentication method to use
authentication: basic
...
```

#### Basic authentication

For [basic authentication](https://de.wikipedia.org/wiki/HTTP-Authentifizierung) you have to provide your username and password in each request to the server. For example in curl you can do this with the `--user` flag:

```bash
curl --username myusername:totalysecretpassword
```

#### Oauth 2

[Oauth 2](https://de.wikipedia.org/wiki/OAuth) is a token based authentication method. Instead of passing your password with each request you request a token from an authorization server. With this token you are then able to authenticate yourself against the pushbits server.

### Message options

Messages are supporting three different syntaxes:
2 changes: 1 addition & 1 deletion cmd/pushbits/main.go
Original file line number Diff line number Diff line change
@@ -60,7 +60,7 @@ func main() {
log.Fatal(err)
}

engine := router.Create(c.Debug, cm, db, dp)
engine := router.Create(c.Debug, cm, db, dp, c.Security.Authentication)

runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port)
}
2 changes: 2 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
@@ -47,6 +47,8 @@ matrix:
security:
# Wether or not to check for weak passwords using HIBP.
checkhibp: false
# The authentication method to use
authentication: basic

crypto:
# Configuration of the KDF for password storage. Do not change unless you know what you are doing!
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -6,11 +6,14 @@ require (
github.com/alexedwards/argon2id v0.0.0-20201228115903-cf543ebc1f7b
github.com/gin-contrib/location v0.0.2
github.com/gin-gonic/gin v1.6.3
github.com/go-oauth2/gin-server v1.0.0
github.com/go-playground/validator/v10 v10.3.0 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd
github.com/google/go-cmp v0.5.0 // indirect
github.com/imrenagi/go-oauth2-mysql v1.1.0
github.com/jinzhu/configor v1.2.1
github.com/jmoiron/sqlx v1.3.3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is related to the separate sqlite file: What's the reason for switching to sqlx when we already have GORM?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see now that it is integrated with oauth. Maybe we can investigate this further. Having two different databases increases complexity.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very clean, but it is possible to use the same mysql database for auth and pushbits itself by just using the same credentials.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I managed to improve that one step further - oauth now does not need any further storage information. If the main db is mysql, oauth uses just the same database and if it is sqlite3 it uses its own sqlite file.

Unfortunately I was not yet able to share the main sqlite with oauth. For that I'd need a sqlx object with the sqlite - which I am able to generate but it then runs in "command unknown" errors as soon as actions are performed on it.

github.com/json-iterator/go v1.1.10 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
@@ -19,6 +22,7 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/ugorji/go v1.2.4 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
gopkg.in/oauth2.v3 v3.12.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/mysql v1.0.4
gorm.io/driver/sqlite v1.1.4
125 changes: 125 additions & 0 deletions go.sum

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions internal/authentication/oauth/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package oauth

import (
"log"
"net/http"

"gopkg.in/oauth2.v3/server"
)

// UserAuthHandler extracts user information from the query
func UserAuthHandler() server.UserAuthorizationHandler {
return func(w http.ResponseWriter, r *http.Request) (string, error) {
// TODO cubicroot check if we need a check here already
log.Println("UserAuthorizationHandler")

return "1", nil
}
}

// PasswordAuthorizationHandler handles username and password based authentication
func PasswordAuthorizationHandler() server.PasswordAuthorizationHandler {
return func(username string, password string) (string, error) {
// TODO cubicroot get user id
log.Println("PW Handler")
return "5", nil
}
}
43 changes: 43 additions & 0 deletions internal/authentication/oauth/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package oauth

import (
ginserver "github.com/go-oauth2/gin-server"
mysql "github.com/imrenagi/go-oauth2-mysql"
"github.com/jmoiron/sqlx"

"gopkg.in/oauth2.v3/manage"
"gopkg.in/oauth2.v3/models"
"gopkg.in/oauth2.v3/server"

"log"
)

// InitializeOauth sets up the basics for oauth authentication
func InitializeOauth() error {
// Initialize the database
dbOauth, err := sqlx.Connect("mysql", "root:FqqVnitR8jkuZZeq8j94@tcp(db-pushbitsdev:3306)/pushbits?parseTime=true") // TODO cubicroot add more options and move to settings
if err != nil {
log.Fatal(err)
}

manager := manage.NewDefaultManager()
manager.MustTokenStorage(mysql.NewTokenStore(dbOauth))

clientStore, _ := mysql.NewClientStore(dbOauth, mysql.WithClientStoreTableName("oauth_clients"))
manager.MapClientStorage(clientStore)

// TODO cubicroot move to settings
clientStore.Create(&models.Client{
ID: "000000",
Secret: "999999",
Domain: "http://localhost",
})

ginserver.InitServer(manager)
ginserver.SetAllowGetAccessRequest(true)
ginserver.SetClientInfoHandler(server.ClientFormHandler)
ginserver.SetUserAuthorizationHandler(UserAuthHandler())
ginserver.SetPasswordAuthorizationHandler(PasswordAuthorizationHandler())

return nil
}
3 changes: 2 additions & 1 deletion internal/configuration/configuration.go
Original file line number Diff line number Diff line change
@@ -45,7 +45,8 @@ type Configuration struct {
Password string `required:"true"`
}
Security struct {
CheckHIBP bool `default:"false"`
CheckHIBP bool `default:"false"`
Authentication string `default:"basic"`
}
Crypto CryptoConfig
Formatting Formatting
34 changes: 33 additions & 1 deletion internal/router/router.go
Original file line number Diff line number Diff line change
@@ -6,15 +6,17 @@ import (
"github.com/pushbits/server/internal/api"
"github.com/pushbits/server/internal/authentication"
"github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/authentication/oauth"
"github.com/pushbits/server/internal/database"
"github.com/pushbits/server/internal/dispatcher"

"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
ginserver "github.com/go-oauth2/gin-server"
)

// Create a Gin engine and setup all routes.
func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *dispatcher.Dispatcher) *gin.Engine {
func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *dispatcher.Dispatcher, authMethod string) *gin.Engine {
log.Println("Setting up HTTP routes.")

if !debug {
@@ -31,6 +33,36 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp
r := gin.Default()

r.Use(location.Default())
// Example from the library: https://github.com/go-oauth2/oauth2/blob/master/example/server/server.go
// Good Tutorial: https://tutorialedge.net/golang/go-oauth2-tutorial/

if authMethod == "oauth" {
oauth.InitializeOauth()

oauthGroup := r.Group("/oauth2")
{
oauthGroup.GET("/token", ginserver.HandleTokenRequest)
// GET TOKEN with client: curl "https://domain.tld/oauth2/token?grant_type=client_credentials&client_id=000000&client_secret=999999&scope=read" -X GET
// GET TOKEN with password: curl "https://domain.tld/oauth2/token?grant_type=password&client_id=000000&client_secret=999999&scope=read&user_id=2&username=alex&password=123" -X GET -i
oauthGroup.GET("/auth", ginserver.HandleAuthorizeRequest)
}

// TODO cubicroot remove - currently only for testing
api := r.Group("/oauthtest")
{
api.Use(ginserver.HandleTokenVerify())
api.GET("/info", func(c *gin.Context) {
ti, exists := c.Get(ginserver.DefaultConfig.TokenKey)
if exists {
c.JSON(200, ti)
return
}
c.String(200, "not found")
})
}
} else {
// TODO cubicroot add other auth methods here
}

applicationGroup := r.Group("/application")
applicationGroup.Use(auth.RequireUser())