Skip to content

Commit

Permalink
Merge pull request #6 from djedjethai/mongo-v4
Browse files Browse the repository at this point in the history
Mongo v4
  • Loading branch information
LyricTian authored Aug 1, 2023
2 parents 64a758f + ba43a9f commit e897970
Show file tree
Hide file tree
Showing 13 changed files with 1,642 additions and 273 deletions.
70 changes: 58 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,70 @@ $ go get -u -v gopkg.in/go-oauth2/mongo.v3
## Usage

``` go
package main

import (
"gopkg.in/go-oauth2/mongo.v3"
"gopkg.in/oauth2.v3/manage"
import(
"github.com/go-oauth2/oauth2/v4/manage"
"github.com/go-oauth2/oauth2/v4/server"
mongo "gopkg.in/go-oauth2/mongo.v3"
)

func main() {
manager := manage.NewDefaultManager()
func main(){
manager := manage.NewDefaultManager()

/*
* only for a MongoDB replicaSet deployment
* Using a replicaSet is recommended as it allows for MongoDB's native support for transactions
**/
// mongoConf := mongo.NewConfigReplicaSet(
// "mongodb://localhost:27017,localhost:28017,localhost:29017/?replicaSet=myReplicaSet",
// "oauth2",
// )

// set connectionTimeout(7s) and the requestsTimeout(5s) // is optional
storeConfigs := mongo.NewStoreConfig(7, 5)

/*
* for a single mongoDB node
* if the oauth2 service is deployed with more than one instance
* each mongoConf should have unique serviceName
**/
mongoConf := mongo.NewConfigNonReplicaSet(
"mongodb://127.0.0.1:27017",
"oauth2", // database name
"admin", // username to authenticate with db
"password", // password to authenticate with db
"serviceName",
)

// use mongodb token store
manager.MapTokenStorage(
mongo.NewTokenStore(mongo.NewConfig(
"mongodb://127.0.0.1:27017",
"oauth2",
)),
mongo.NewTokenStore(mongoConf, storeConfigs), // with timeout
// mongo.NewTokenStore(mongoConf), // no timeout
)
// ...

clientStore := mongo.NewClientStore(mongoConf, storeConfigs) // with timeout
// clientStore := mongo.NewClientStore(mongoConf) // no timeout

manager.MapClientStorage(clientStore)

// register a service
clientStore.Create(&models.Client{
ID: idvar,
Secret: secretvar,
Domain: domainvar,
UserID: "frontend",
})

// register a second service
clientStore.Create(&models.Client{
ID: idPreorder,
Secret: secretPreorder,
Domain: domainPreorder,
UserID: "prePost",
})

srv := server.NewServer(server.NewConfig(), manager)

// ...
}
```

Expand Down
243 changes: 184 additions & 59 deletions client_store.go
Original file line number Diff line number Diff line change
@@ -1,121 +1,246 @@
package mongo

import (
"github.com/globalsign/mgo"
"gopkg.in/oauth2.v3"
"gopkg.in/oauth2.v3/models"
"context"
"errors"
"log"
"time"

"github.com/go-oauth2/oauth2/v4"
"github.com/go-oauth2/oauth2/v4/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)

// TODO
// add retry mecanism on all requests
// if retry fail ping the db
// if ping fail crash the service
// that should be optional as a service's restart mecanism should be implemented

// StoreConfig hold configs common to all Configs(ClientConfig, TokenConfig)
type StoreConfig struct {
db string
service string
connectionTimeout int
requestTimeout int
isReplicaSet bool
}

func NewStoreConfig(ctout, rtout int) *StoreConfig {
return &StoreConfig{
connectionTimeout: ctout,
requestTimeout: rtout,
}
}

func NewDefaultStoreConfig(db, service string, isReplicasSet bool) *StoreConfig {
return &StoreConfig{
db: db,
service: service,
connectionTimeout: 0,
requestTimeout: 0,
isReplicaSet: isReplicasSet,
}
}

// setRequestContext set a WithTimeout or Background context
func (sc *StoreConfig) setRequestContext() (context.Context, context.CancelFunc) {
ctx := context.Background()
if sc.requestTimeout > 0 {
log.Println("Request timeout: ", sc.requestTimeout)
timeout := time.Duration(sc.requestTimeout) * time.Second
return context.WithTimeout(ctx, timeout)
}
return nil, func() {}
}

// setTransactionCreateContext is specific to the transaction(if not a replicaSet)
func (sc *StoreConfig) setTransactionCreateContext() (context.Context, context.CancelFunc) {
ctx := context.Background()
if sc.requestTimeout > 0 {
// at max TransactionCreate run 9 requests
timeout := time.Duration(sc.requestTimeout*9) * time.Second
return context.WithTimeout(ctx, timeout)
}
return nil, func() {}
}

// ClientConfig client configuration parameters
type ClientConfig struct {
// store clients data collection name(The default is oauth2_clients)
ClientsCName string
storeConfig *StoreConfig
}

// NewDefaultClientConfig create a default client configuration
func NewDefaultClientConfig() *ClientConfig {
func NewDefaultClientConfig(strCfgs *StoreConfig) *ClientConfig {
return &ClientConfig{
ClientsCName: "oauth2_clients",
storeConfig: strCfgs,
}
}

// NewClientStore create a client store instance based on mongodb
func NewClientStore(cfg *Config, ccfgs ...*ClientConfig) *ClientStore {
session, err := mgo.Dial(cfg.URL)
func NewClientStore(cfg *Config, scfgs ...*StoreConfig) *ClientStore {
clientOptions := options.Client().ApplyURI(cfg.URL)
ctx := context.TODO()
ctxPing := context.TODO()

if len(scfgs) > 0 && scfgs[0].connectionTimeout > 0 {
newCtx, cancel := context.WithTimeout(context.Background(), time.Duration(scfgs[0].connectionTimeout)*time.Second)
ctx = newCtx
defer cancel()
clientOptions.SetConnectTimeout(time.Duration(scfgs[0].connectionTimeout) * time.Second)
}

if len(scfgs) > 0 && scfgs[0].requestTimeout > 0 {
newCtx, cancel := context.WithTimeout(context.Background(), time.Duration(scfgs[0].requestTimeout)*time.Second)
ctxPing = newCtx
defer cancel()
clientOptions.SetConnectTimeout(time.Duration(scfgs[0].requestTimeout) * time.Second)
}

if !cfg.IsReplicaSet {
clientOptions.SetAuth(options.Credential{
Username: cfg.Username,
Password: cfg.Password,
})
}

c, err := mongo.Connect(ctx, clientOptions)
if err != nil {
log.Fatal("ClientStore failed to connect mongo: ", err)
} else {
log.Println("Connection to mongoDB successful")
}

err = c.Ping(ctxPing, nil)
if err != nil {
panic(err)
log.Fatal("MongoDB ping failed:", err)
}

return NewClientStoreWithSession(session, cfg.DB, ccfgs...)
log.Println("Ping db successfull")

return NewClientStoreWithSession(c, cfg, scfgs...)
}

// NewClientStoreWithSession create a client store instance based on mongodb
func NewClientStoreWithSession(session *mgo.Session, dbName string, ccfgs ...*ClientConfig) *ClientStore {
func NewClientStoreWithSession(client *mongo.Client, cfg *Config, scfgs ...*StoreConfig) *ClientStore {
strCfgs := NewDefaultStoreConfig(cfg.DB, cfg.Service, cfg.IsReplicaSet)

cs := &ClientStore{
dbName: dbName,
session: session,
ccfg: NewDefaultClientConfig(),
client: client,
ccfg: NewDefaultClientConfig(strCfgs),
}
if len(ccfgs) > 0 {
cs.ccfg = ccfgs[0]

if len(scfgs) > 0 {
if scfgs[0].connectionTimeout > 0 {
cs.ccfg.storeConfig.connectionTimeout = scfgs[0].connectionTimeout
}
if scfgs[0].requestTimeout > 0 {
cs.ccfg.storeConfig.requestTimeout = scfgs[0].requestTimeout
}
}

return cs
}

// ClientStore MongoDB storage for OAuth 2.0
type ClientStore struct {
ccfg *ClientConfig
dbName string
session *mgo.Session
ccfg *ClientConfig
client *mongo.Client
}

// Close close the mongo session
func (cs *ClientStore) Close() {
cs.session.Close()
if err := cs.client.Disconnect(context.Background()); err != nil {
log.Fatal(err)
}
}

func (cs *ClientStore) c(name string) *mgo.Collection {
return cs.session.DB(cs.dbName).C(name)
func (cs *ClientStore) c(name string) *mongo.Collection {
return cs.client.Database(cs.ccfg.storeConfig.db).Collection(name)
}

func (cs *ClientStore) cHandler(name string, handler func(c *mgo.Collection)) {
session := cs.session.Clone()
defer session.Close()
handler(session.DB(cs.dbName).C(name))
return
}
// Create create client information
func (cs *ClientStore) Create(info oauth2.ClientInfo) (err error) {
ctx := context.Background()

// Set set client information
func (cs *ClientStore) Set(info oauth2.ClientInfo) (err error) {
cs.cHandler(cs.ccfg.ClientsCName, func(c *mgo.Collection) {
entity := &client{
ID: info.GetID(),
Secret: info.GetSecret(),
Domain: info.GetDomain(),
UserID: info.GetUserID(),
}
ctxReq, cancel := cs.ccfg.storeConfig.setRequestContext()
defer cancel()
if ctxReq != nil {
ctx = ctxReq
}

entity := &client{
ID: info.GetID(),
Secret: info.GetSecret(),
Domain: info.GetDomain(),
UserID: info.GetUserID(),
}

collection := cs.c(cs.ccfg.ClientsCName)

if cerr := c.Insert(entity); cerr != nil {
err = cerr
return
_, err = collection.InsertOne(ctx, entity)
if err != nil {
if !mongo.IsDuplicateKeyError(err) {
log.Fatal(err)
} else {
return nil
}
})
}

return
}

// GetByID according to the ID for the client information
func (cs *ClientStore) GetByID(id string) (info oauth2.ClientInfo, err error) {
cs.cHandler(cs.ccfg.ClientsCName, func(c *mgo.Collection) {
entity := new(client)
func (cs *ClientStore) GetByID(ctx context.Context, id string) (info oauth2.ClientInfo, err error) {
ctxReq, cancel := cs.ccfg.storeConfig.setRequestContext()
defer cancel()
if ctxReq != nil {
ctx = ctxReq
}

if cerr := c.FindId(id).One(entity); cerr != nil {
err = cerr
return
filter := bson.M{"_id": id}
result := cs.c(cs.ccfg.ClientsCName).FindOne(ctx, filter)
if err := result.Err(); err != nil {
if err == mongo.ErrNoDocuments {
return nil, err
}
return nil, errors.New("Internal server error, no client found for this ID")

info = &models.Client{
ID: entity.ID,
Secret: entity.Secret,
Domain: entity.Domain,
UserID: entity.UserID,
}
})
}

entity := &client{}
if err := result.Decode(entity); err != nil {
log.Println(err)
}

info = &models.Client{
ID: entity.ID,
Secret: entity.Secret,
Domain: entity.Domain,
UserID: entity.UserID,
}

return
}

// RemoveByID use the client id to delete the client information
func (cs *ClientStore) RemoveByID(id string) (err error) {
cs.cHandler(cs.ccfg.ClientsCName, func(c *mgo.Collection) {
if cerr := c.RemoveId(id); cerr != nil {
err = cerr
return
}
})
ctx := context.Background()

return
ctxReq, cancel := cs.ccfg.storeConfig.setRequestContext()
defer cancel()
if ctxReq != nil {
ctx = ctxReq
}

filter := bson.M{"_id": id}
_, err = cs.c(cs.ccfg.ClientsCName).DeleteOne(ctx, filter)
return err
}

type client struct {
Expand Down
Loading

0 comments on commit e897970

Please sign in to comment.