use crypto/rand

This commit is contained in:
dhax 2017-09-26 23:39:23 +02:00
parent 4f5abfbd70
commit 03e7b58b64
6 changed files with 44 additions and 26 deletions

3
.gitignore vendored
View file

@ -2,6 +2,9 @@
.vscode .vscode
.realize .realize
debug
go-base
# Binaries for programs and plugins # Binaries for programs and plugins
*.exe *.exe
*.dll *.dll

View file

@ -4,15 +4,30 @@
Easily extendible RESTful API boilerplate aiming to follow idiomatic go and best practice. Easily extendible RESTful API boilerplate aiming to follow idiomatic go and best practice.
### Features ### Features
* PostgreSQL support including migrations using [go-pg](https://github.com/go-pg/pg) - Configuration using [viper](https://github.com/spf13/viper)
* Structured logging with [Logrus](https://github.com/sirupsen/logrus) - CLI features using [cobra](https://github.com/spf13/cobra)
* Routing with [chi router](https://github.com/go-chi/chi) - [dep](https://github.com/golang/dep) for dependency management
* JWT Authentication using [jwt-go](https://github.com/dgrijalva/jwt-go) with passwordless email authentication (could be easily extended to use passwords instead) - PostgreSQL support including migrations using [go-pg](https://github.com/go-pg/pg)
* Configuration using [viper](https://github.com/spf13/viper) - Structured logging with [Logrus](https://github.com/sirupsen/logrus)
* CLI features using [cobra](https://github.com/spf13/cobra) - Routing with [chi router](https://github.com/go-chi/chi) and middleware
* [dep](https://github.com/golang/dep) for dependency management - JWT Authentication using [jwt-go](https://github.com/dgrijalva/jwt-go) in combination with passwordless email authentication (could be easily extended to use passwords instead)
- Request data validation using [ozzo-validation](https://github.com/go-ozzo/ozzo-validation)
- HTML emails with [gomail](https://github.com/go-gomail/gomail)
### Start Application
- Clone this repository
- Create a postgres database and set environment variable *DATABASE_URL* accordingly if not using same as default
- Build the application: ```go build``` to create ```go-base``` binary or use ```go run main.go``` instead in the following commands
- Initialize the database migrations table: ```go-base migrate init```
- Run all migrations found in ./database/migrate with: ```go-base migrate```
- Run the application: ```go-base serve```
#### Demo client application
For demonstration of the login and account management features this API also serves a Single Page Application (SPA) as a Progressive Web App (PWA) done with [Quasar Framework](http://quasar-framework.org) which itself is powered by [Vue.js](https://vuejs.org). The client's source code can be found [here](https://github.com/dhax/go-base-client).
If no valid email smtp settings are provided by environment variables, emails will be print to stdout showing the login token. Use one of the following users for login:
- admin@boot.io (has access to admin panel)
- user@boot.io
### Environment Variables ### Environment Variables

View file

@ -118,7 +118,7 @@ func (rs *AccountResource) list(w http.ResponseWriter, r *http.Request) {
func (rs *AccountResource) create(w http.ResponseWriter, r *http.Request) { func (rs *AccountResource) create(w http.ResponseWriter, r *http.Request) {
data := &accountRequest{} data := &accountRequest{}
if err := render.Bind(r, data); err != nil { if err := render.Bind(r, data); err != nil {
render.Respond(w, r, ErrInvalidRequest(err)) render.Render(w, r, ErrInvalidRequest(err))
return return
} }

View file

@ -145,12 +145,12 @@ func (d *tokenRequest) Bind(r *http.Request) error {
func (rs *AccountResource) updateToken(w http.ResponseWriter, r *http.Request) { func (rs *AccountResource) updateToken(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "tokenID")) id, err := strconv.Atoi(chi.URLParam(r, "tokenID"))
if err != nil { if err != nil {
render.Respond(w, r, ErrBadRequest) render.Render(w, r, ErrBadRequest)
return return
} }
data := &tokenRequest{} data := &tokenRequest{}
if err := render.Bind(r, data); err != nil { if err := render.Bind(r, data); err != nil {
render.Respond(w, r, ErrInvalidRequest(err)) render.Render(w, r, ErrInvalidRequest(err))
return return
} }
acc := r.Context().Value(ctxAccount).(*models.Account) acc := r.Context().Value(ctxAccount).(*models.Account)
@ -160,7 +160,7 @@ func (rs *AccountResource) updateToken(w http.ResponseWriter, r *http.Request) {
ID: t.ID, ID: t.ID,
Identifier: data.Identifier, Identifier: data.Identifier,
}); err != nil { }); err != nil {
render.Respond(w, r, ErrInvalidRequest(err)) render.Render(w, r, ErrInvalidRequest(err))
return return
} }
} }
@ -171,7 +171,7 @@ func (rs *AccountResource) updateToken(w http.ResponseWriter, r *http.Request) {
func (rs *AccountResource) deleteToken(w http.ResponseWriter, r *http.Request) { func (rs *AccountResource) deleteToken(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "tokenID")) id, err := strconv.Atoi(chi.URLParam(r, "tokenID"))
if err != nil { if err != nil {
render.Respond(w, r, ErrBadRequest) render.Render(w, r, ErrBadRequest)
return return
} }
acc := r.Context().Value(ctxAccount).(*models.Account) acc := r.Context().Value(ctxAccount).(*models.Account)

View file

@ -1,20 +1,20 @@
package auth package auth
import ( import (
"math/rand" "crypto/rand"
"time"
) )
func init() {
rand.Seed(time.Now().UnixNano())
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func randStringBytes(n int) string { func randStringBytes(n int) string {
b := make([]byte, n) buf := make([]byte, n)
for i := range b { _, err := rand.Read(buf)
b[i] = letterBytes[rand.Intn(len(letterBytes))] if err != nil {
panic(err)
} }
return string(b)
for k, v := range buf {
buf[k] = letterBytes[v%byte(len(letterBytes))]
}
return string(buf)
} }

View file

@ -139,7 +139,7 @@ func (rs *Resource) token(w http.ResponseWriter, r *http.Request) {
if err := rs.store.SaveRefreshToken(token); err != nil { if err := rs.store.SaveRefreshToken(token); err != nil {
log(r).Error(err) log(r).Error(err)
render.Respond(w, r, ErrInternalServerError) render.Render(w, r, ErrInternalServerError)
return return
} }
@ -148,7 +148,7 @@ func (rs *Resource) token(w http.ResponseWriter, r *http.Request) {
acc.LastLogin = time.Now() acc.LastLogin = time.Now()
if err := rs.store.UpdateAccount(acc); err != nil { if err := rs.store.UpdateAccount(acc); err != nil {
log(r).Error(err) log(r).Error(err)
render.Respond(w, r, ErrInternalServerError) render.Render(w, r, ErrInternalServerError)
return return
} }
@ -185,14 +185,14 @@ func (rs *Resource) refresh(w http.ResponseWriter, r *http.Request) {
access, refresh := rs.Token.GenTokenPair(acc, token) access, refresh := rs.Token.GenTokenPair(acc, token)
if err := rs.store.SaveRefreshToken(token); err != nil { if err := rs.store.SaveRefreshToken(token); err != nil {
log(r).Error(err) log(r).Error(err)
render.Respond(w, r, ErrInternalServerError) render.Render(w, r, ErrInternalServerError)
return return
} }
acc.LastLogin = time.Now() acc.LastLogin = time.Now()
if err := rs.store.UpdateAccount(acc); err != nil { if err := rs.store.UpdateAccount(acc); err != nil {
log(r).Error(err) log(r).Error(err)
render.Respond(w, r, ErrInternalServerError) render.Render(w, r, ErrInternalServerError)
return return
} }