1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-09 13:05:56 +00:00

more features and fix database/mysql:jwt example

This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-11-24 14:58:02 +02:00
parent 4d857ac53f
commit 11e21150d0
24 changed files with 767 additions and 153 deletions

View File

@@ -196,7 +196,11 @@
* [Ttemplates and Functions](i18n/template)
* [Pluralization and Variables](i18n/plurals)
* Authentication, Authorization & Bot Detection
* [Basic Authentication](auth/basicauth/main.go)
* Basic Authentication
* [Basic](auth/basicauth/basic)
* [Load from a slice of Users](auth/basicauth/users_list)
* [Load from a file & encrypted passwords](auth/basicauth/users_file_bcrypt)
* [Fetch & validate a User from a Database (MySQL)](auth/basicauth/database)
* [CORS](auth/cors)
* JSON Web Tokens
* [Basic](auth/jwt/basic/main.go)

View File

@@ -44,12 +44,12 @@ func newApp() *iris.Application {
needAuth := app.Party("/admin", auth)
{
//http://localhost:8080/admin
needAuth.Get("/", h)
needAuth.Get("/", handler)
// http://localhost:8080/admin/profile
needAuth.Get("/profile", h)
needAuth.Get("/profile", handler)
// http://localhost:8080/admin/settings
needAuth.Get("/settings", h)
needAuth.Get("/settings", handler)
needAuth.Get("/logout", logout)
}
@@ -63,7 +63,7 @@ func main() {
app.Listen(":8080")
}
func h(ctx iris.Context) {
func handler(ctx iris.Context) {
// username, password, _ := ctx.Request().BasicAuth()
// third parameter it will be always true because the middleware
// makes sure for that, otherwise this handler will not be executed.

View File

@@ -25,5 +25,5 @@ func TestBasicAuth(t *testing.T) {
// with invalid basic auth
e.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword").
Expect().Status(httptest.StatusForbidden)
Expect().Status(httptest.StatusUnauthorized)
}

View File

@@ -0,0 +1,18 @@
# docker build -t myapp .
# docker run --rm -it -p 8080:8080 myapp:latest
FROM golang:latest AS builder
RUN apt-get update
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /go/src/app
COPY go.mod .
RUN go mod download
# cache step
COPY . .
RUN go install
FROM scratch
COPY --from=builder /go/bin/myapp .
ENTRYPOINT ["./myapp"]

View File

@@ -0,0 +1,44 @@
# BasicAuth + MySQL & Docker Example
## ⚡ Get Started
Download the folder.
### Install (Docker)
Install [Docker](https://www.docker.com/) and execute the command below
```sh
$ docker-compose up --build
```
### Install (Manually)
Run `go build` or `go run main.go` and read below.
#### MySQL
Environment variables:
```sh
MYSQL_USER=user_myapp
MYSQL_PASSWORD=dbpassword
MYSQL_HOST=localhost
MYSQL_DATABASE=myapp
```
Download the schema from [migration/db.sql](migration/db.sql) and execute it against your MySQL server instance.
<http://localhost:8080>
```sh
username: admin
password: admin
```
```sh
username: iris
password: iris_password
```
The example does not contain code to add a user to the database, as this is out of the scope of this middleware. More features can be implemented by end-developers.

View File

@@ -0,0 +1,32 @@
version: '3.1'
services:
db:
image: mysql
command: --default-authentication-plugin=mysql_native_password
environment:
MYSQL_ROOT_PASSWORD: dbpassword
MYSQL_DATABASE: myapp
MYSQL_USER: user_myapp
MYSQL_PASSWORD: dbpassword
tty: true
volumes:
- ./migration:/docker-entrypoint-initdb.d
app:
build: .
ports:
- 8080:8080
environment:
PORT: 8080
MYSQL_USER: user_myapp
MYSQL_PASSWORD: dbpassword
MYSQL_DATABASE: myapp
MYSQL_HOST: db
restart: on-failure
healthcheck:
test: ["CMD", "curl", "-f", "tcp://db:3306"]
interval: 30s
timeout: 10s
retries: 10
depends_on:
- db

View File

@@ -0,0 +1,8 @@
module myapp
go 1.15
require (
github.com/go-sql-driver/mysql v1.5.0
github.com/kataras/iris/v12 master
)

View File

@@ -0,0 +1,113 @@
package main
import (
"context"
"database/sql"
"fmt"
"os"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
_ "github.com/go-sql-driver/mysql" // lint: mysql driver.
)
// User is just an example structure of a user,
// it MUST contain a Username and Password exported fields
// or/and complete the basicauth.User interface.
type User struct {
ID int64 `db:"id" json:"id"`
Username string `db:"username" json:"username"`
Password string `db:"password" json:"password"`
Email string `db:"email" json:"email"`
}
// GetUsername returns the Username field.
func (u User) GetUsername() string {
return u.Username
}
// GetPassword returns the Password field.
func (u User) GetPassword() string {
return u.Password
}
func main() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
getenv("MYSQL_USER", "user_myapp"),
getenv("MYSQL_PASSWORD", "dbpassword"),
getenv("MYSQL_HOST", "localhost"),
getenv("MYSQL_DATABASE", "myapp"),
)
db, err := connect(dsn)
if err != nil {
panic(err)
}
// Validate a user from database.
allowFunc := func(ctx iris.Context, username, password string) (interface{}, bool) {
user, err := db.getUserByUsernameAndPassword(context.Background(), username, password)
return user, err == nil
}
opts := basicauth.Options{
Realm: basicauth.DefaultRealm,
ErrorHandler: basicauth.DefaultErrorHandler,
Allow: allowFunc,
}
auth := basicauth.New(opts)
app := iris.New()
app.Use(auth)
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
user := ctx.User()
ctx.JSON(user)
}
func getenv(key string, def string) string {
v := os.Getenv(key)
if v == "" {
return def
}
return v
}
type database struct {
*sql.DB
}
func connect(dsn string) (*database, error) {
conn, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
err = conn.Ping()
if err != nil {
conn.Close()
return nil, err
}
return &database{conn}, nil
}
func (db *database) getUserByUsernameAndPassword(ctx context.Context, username, password string) (User, error) {
query := fmt.Sprintf("SELECT * FROM %s WHERE %s = ? AND %s = ? LIMIT 1", "users", "username", "password")
rows, err := db.QueryContext(ctx, query, username, password)
if err != nil {
return User{}, err
}
defer rows.Close()
if !rows.Next() {
return User{}, sql.ErrNoRows
}
var user User
err = rows.Scan(&user.ID, &user.Username, &user.Password, &user.Email)
return user, err
}

View File

@@ -0,0 +1,22 @@
CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE myapp;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
email varchar(255) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO users (username,password,email)
VALUES
('admin', 'admin', 'kataras2006@hotmail.com'),
("iris", 'iris_password', 'iris-go@outlook.com');
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,30 @@
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
)
func main() {
auth := basicauth.Load("users.yml", basicauth.BCRYPT)
/* Same as:
opts := basicauth.Options{
Realm: basicauth.DefaultRealm,
Allow: basicauth.AllowUsersFile("users.yml", basicauth.BCRYPT),
}
auth := basicauth.New(opts)
*/
app := iris.New()
app.Use(auth)
app.Get("/", index)
// kataras:kataras_pass
// makis:makis_pass
app.Listen(":8080")
}
func index(ctx iris.Context) {
user := ctx.User()
ctx.JSON(user)
}

View File

@@ -0,0 +1,12 @@
# The file cannot be modified during the serve time.
# To support real-time users changes please use the Options.Allow instead,
# (see the database example for that).
#
# Again, the username and password (or capitalized) fields are required,
# the rest are optional, depending on your application needs.
- username: kataras
password: $2a$10$Irg8k8HWkDlvL0YDBKLCYee6j6zzIFTplJcvZYKA.B8/clHPZn2Ey # encrypted of kataras_pass
age: 27
role: admin
- username: makis
password: $2a$10$3GXzp3J5GhHThGisbpvpZuftbmzPivDMo94XPnkTnDe7254x7sJ3O # encrypted of makis_pass

View File

@@ -0,0 +1,58 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
)
// User is just an example structure of a user,
// it MUST contain a Username and Password exported fields
// or complete the basicauth.User interface.
type User struct {
Username string `json:"username"`
Password string `json:"password"`
Roles []string `json:"roles"`
}
var users = []User{
{"admin", "admin", []string{"admin"}},
{"kataras", "kataras_pass", []string{"manager", "author"}},
{"george", "george_pass", []string{"member"}},
{"john", "john_pass", []string{}},
}
func main() {
opts := basicauth.Options{
Realm: basicauth.DefaultRealm,
// Defaults to 0, no expiration.
// Prompt for new credentials on a client's request
// made after 10 minutes the user has logged in:
MaxAge: 10 * time.Minute,
// Clear any expired users from the memory every one hour,
// note that the user's expiration time will be
// reseted on the next valid request (when Allow passed).
GC: basicauth.GC{
Every: 2 * time.Hour,
},
// The users can be a slice of custom users structure
// or a map[string]string (username:password)
// or []map[string]interface{} with username and passwords required fields,
// read the godocs for more.
Allow: basicauth.AllowUsers(users),
}
auth := basicauth.New(opts)
// OR: basicauth.Default(users)
app := iris.New()
app.Use(auth)
app.Get("/", index)
app.Listen(":8080")
}
func index(ctx iris.Context) {
user, _ := ctx.User().GetRaw()
ctx.JSON(user)
}

View File

@@ -89,7 +89,7 @@ MYSQL_HOST=localhost
MYSQL_DATABASE=myapp
```
Download the schema from [migration/myapp.sql](migration/myapp.sql) and execute it against your MySQL server instance.
Download the schema from [migration/db.sql](migration/db.sql) and execute it against your MySQL server instance.
```sql
CREATE DATABASE IF NOT EXISTS myapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -139,7 +139,7 @@ Testing is important. The code is written in a way that testing should be trivia
## Packages
- https://github.com/dgrijalva/jwt-go (JWT parsing)
- https://github.com/kataras/jwt (JWT parsing)
- https://github.com/go-sql-driver/mysql (Go Driver for MySQL)
- https://github.com/DATA-DOG/go-sqlmock (Testing DB see [service/category_service_test.go](service/category_service_test.go))
- https://github.com/kataras/iris (HTTP)

View File

@@ -15,18 +15,19 @@ import (
// Router accepts any required dependencies and returns the main server's handler.
func Router(db sql.Database, secret string) func(iris.Party) {
return func(r iris.Party) {
j := jwt.HMAC(15*time.Minute, secret)
r.Use(requestid.New())
r.Use(verifyToken(j))
signer := jwt.NewSigner(jwt.HS256, secret, 15*time.Minute)
r.Get("/token", writeToken(signer))
verify := jwt.NewVerifier(jwt.HS256, secret).Verify(nil)
r.Use(verify)
// Generate a token for testing by navigating to
// http://localhost:8080/token endpoint.
// Copy-paste it to a ?token=$token url parameter or
// open postman and put an Authentication: Bearer $token to get
// access on create, update and delete endpoinds.
r.Get("/token", writeToken(j))
var (
categoryService = service.NewCategoryService(db)
productService = service.NewProductService(db)
@@ -73,25 +74,19 @@ func Router(db sql.Database, secret string) func(iris.Party) {
}
}
func writeToken(j *jwt.JWT) iris.Handler {
func writeToken(signer *jwt.Signer) iris.Handler {
return func(ctx iris.Context) {
claims := jwt.Claims{
Issuer: "https://iris-go.com",
Audience: jwt.Audience{requestid.Get(ctx)},
Audience: []string{requestid.Get(ctx)},
}
j.WriteToken(ctx, claims)
}
}
func verifyToken(j *jwt.JWT) iris.Handler {
return func(ctx iris.Context) {
// Allow all GET.
if ctx.Method() == iris.MethodGet {
ctx.Next()
token, err := signer.Sign(claims)
if err != nil {
ctx.StopWithStatus(iris.StatusInternalServerError)
return
}
j.Verify(ctx)
ctx.Write(token)
}
}

View File

@@ -4,6 +4,6 @@ go 1.15
require (
github.com/go-sql-driver/mysql v1.5.0
github.com/kataras/iris/v12 master
github.com/kataras/iris/v12 v12.2.0-alpha.0.20201117050536-962ffd67721a
github.com/mailgun/groupcache/v2 v2.1.0
)

View File

@@ -11,6 +11,8 @@ import (
"github.com/kataras/iris/v12"
)
// $ go build .
func main() {
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?parseTime=true&charset=utf8mb4&collation=utf8mb4_unicode_ci",
getenv("MYSQL_USER", "user_myapp"),