Browse Source

Merge pull request #1677 from kataras/new-basicauth-features

New Basic Authentication Features
pull/1686/head
Gerasimos (Makis) Maropoulos 1 year ago
committed by GitHub
parent
commit
288646a31c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      HISTORY.md
  2. 6
      _examples/README.md
  3. 44
      _examples/auth/basicauth/basic/main.go
  4. 0
      _examples/auth/basicauth/basic/main_test.go
  5. 18
      _examples/auth/basicauth/database/Dockerfile
  6. 44
      _examples/auth/basicauth/database/README.md
  7. 32
      _examples/auth/basicauth/database/docker-compose.yml
  8. 8
      _examples/auth/basicauth/database/go.mod
  9. 114
      _examples/auth/basicauth/database/main.go
  10. 22
      _examples/auth/basicauth/database/migration/db.sql
  11. 30
      _examples/auth/basicauth/users_file_bcrypt/main.go
  12. 12
      _examples/auth/basicauth/users_file_bcrypt/users.yml
  13. 58
      _examples/auth/basicauth/users_list/main.go
  14. 2
      _examples/auth/jwt/tutorial/go.mod
  15. 16
      _examples/database/mysql/README.md
  16. 29
      _examples/database/mysql/api/api.go
  17. 7
      _examples/database/mysql/api/category_handler.go
  18. 2
      _examples/database/mysql/main.go
  19. 121
      _examples/database/mysql/migration/api_postman.json
  20. 6
      _examples/dependency-injection/overview/web/middleware/basicauth.go
  21. 6
      _examples/file-server/file-server/main.go
  22. 6
      _examples/mvc/login/web/middleware/basicauth.go
  23. 6
      _examples/mvc/repository/web/middleware/basicauth.go
  24. 23
      _examples/routing/rewrite/main.go
  25. 2
      _examples/routing/rewrite/redirects.yml
  26. 10
      _examples/testing/httptest/main.go
  27. 39
      context/context.go
  28. 86
      context/context_user.go
  29. 10
      go.mod
  30. 642
      middleware/basicauth/basicauth.go
  31. 24
      middleware/basicauth/basicauth_test.go
  32. 59
      middleware/basicauth/config.go
  33. 108
      middleware/basicauth/error.go
  34. 88
      middleware/basicauth/header.go
  35. 103
      middleware/basicauth/header_test.go
  36. 300
      middleware/basicauth/user.go
  37. 288
      middleware/basicauth/user_test.go

2
HISTORY.md

@ -30,7 +30,7 @@ The codebase for Dependency Injection, Internationalization and localization and
- Add `iris.DirOptions.SPA bool` field to allow [Single Page Applications](https://github.com/kataras/iris/tree/master/_examples/file-server/single-page-application/basic/main.go) under a file server.
- A generic User interface, see the `Context.SetUser/User` methods in the New Context Methods section for more. In-short, the basicauth middleware's stored user can now be retrieved through `Context.User()` which provides more information than the native `ctx.Request().BasicAuth()` method one. Third-party authentication middleware creators can benefit of these two methods, plus the Logout below.
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) or [jwt](https://github.com/kataras/iris/blob/master/_examples/auth/jwt/blocklist/main.go) client credentials.
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/basic/main.go) or [jwt](https://github.com/kataras/iris/blob/master/_examples/auth/jwt/blocklist/main.go) client credentials.
- Add the ability to [share functions](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) between handlers chain and add an [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-services) on sharing Go structures (aka services).
- Add the new `Party.UseOnce` method to the `*Route`

6
_examples/README.md

@ -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)

44
_examples/auth/basicauth/main.go → _examples/auth/basicauth/basic/main.go

@ -1,8 +1,6 @@
package main
import (
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/basicauth"
)
@ -10,33 +8,48 @@ import (
func newApp() *iris.Application {
app := iris.New()
authConfig := basicauth.Config{
Users: map[string]string{"myusername": "mypassword", "mySecondusername": "mySecondpassword"},
Realm: "Authorization Required", // defaults to "Authorization Required"
Expires: time.Duration(30) * time.Minute,
}
/*
opts := basicauth.Options{
Realm: "Authorization Required",
MaxAge: 30 * time.Minute,
GC: basicauth.GC{
Every: 2 * time.Hour,
},
Allow: basicauth.AllowUsers(map[string]string{
"myusername": "mypassword",
"mySecondusername": "mySecondpassword",
}),
MaxTries: 2,
}
auth := basicauth.New(opts)
OR simply:
*/
authentication := basicauth.New(authConfig)
auth := basicauth.Default(map[string]string{
"myusername": "mypassword",
"mySecondusername": "mySecondpassword",
})
// to global app.Use(authentication) (or app.UseGlobal before the .Run)
// to global app.Use(auth) (or app.UseGlobal before the .Run)
// to routes
/*
app.Get("/mysecret", authentication, h)
app.Get("/mysecret", auth, h)
*/
app.Get("/", func(ctx iris.Context) { ctx.Redirect("/admin") })
// to party
needAuth := app.Party("/admin", authentication)
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)
}
@ -50,12 +63,13 @@ 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.
// OR:
user := ctx.User()
// OR ctx.User().GetRaw() to get the underline value.
username, _ := user.GetUsername()
password, _ := user.GetPassword()
ctx.Writef("%s %s:%s", ctx.Path(), username, password)

0
_examples/auth/basicauth/main_test.go → _examples/auth/basicauth/basic/main_test.go

18
_examples/auth/basicauth/database/Dockerfile

@ -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"]

44
_examples/auth/basicauth/database/README.md

@ -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.

32
_examples/auth/basicauth/database/docker-compose.yml

@ -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

8
_examples/auth/basicauth/database/go.mod

@ -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
)

114
_examples/auth/basicauth/database/main.go

@ -0,0 +1,114 @@
package main // Look README.md
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().GetRaw()
// user is a type of main.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
}

22
_examples/auth/basicauth/database/migration/db.sql

@ -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;

30
_examples/auth/basicauth/users_file_bcrypt/main.go

@ -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)
}

12
_examples/auth/basicauth/users_file_bcrypt/users.yml

@ -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

58
_examples/auth/basicauth/users_list/main.go

@ -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)
}

2
_examples/auth/jwt/tutorial/go.mod

@ -4,7 +4,7 @@ go 1.15
require (
github.com/google/uuid v1.1.2
github.com/kataras/iris/v12 v12.2.0-alpha.0.20201106220849-7a19cfb2112f
github.com/kataras/iris/v12 v12.2.0-alpha.0.20201113181155-4d09475c290d
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
)

16
_examples/database/mysql/README.md

@ -5,19 +5,19 @@
| Method | Path | Description | URL Parameters | Body | Auth Required |
|--------|---------------------|------------------------|--------------- |----------------------------|---------------|
| ANY | /token | Prints a new JWT Token | - | - | - |
| GET | /category | Lists a set of Categories | offset, limit, order | - | - |
| GET | /category | Lists a set of Categories | offset, limit, order | - | Token |
| POST | /category | Creates a Category | - | JSON [Full Category](migration/api_category/create_category.json) | Token |
| PUT | /category | Fully-Updates a Category | - | JSON [Full Category](migration/api_category/update_category.json) | Token |
| PATCH | /category/{id} | Partially-Updates a Category | - | JSON [Partial Category](migration/api_category/update_partial_category.json) | Token |
| GET | /category/{id} | Prints a Category | - | - | - |
| GET | /category/{id} | Prints a Category | - | - | Token |
| DELETE | /category/{id} | Deletes a Category | - | - | Token |
| GET | /category/{id}/products | Lists all Products from a Category | offset, limit, order | - | - |
| GET | /category/{id}/products | Lists all Products from a Category | offset, limit, order | - | Token |
| POST | /category/{id}/products | (Batch) Assigns one or more Products to a Category | - | JSON [Products](migration/api_category/insert_products_category.json) | Token |
| GET | /product | Lists a set of Products (cache) | offset, limit, order | - | - |
| GET | /product | Lists a set of Products (cache) | offset, limit, order | - | Token |
| POST | /product | Creates a Product | - | JSON [Full Product](migration/api_product/create_product.json) | Token |
| PUT | /product | Fully-Updates a Product | - | JSON [Full Product](migration/api_product/update_product.json) | Token |
| PATCH | /product/{id} | Partially-Updates a Product | - | JSON [Partial Product](migration/api_product/update_partial_product.json) | Token |
| GET | /product/{id} | Prints a Product (cache) | - | - | - |
| GET | /product/{id} | Prints a Product (cache) | - | - | Token |
| DELETE | /product/{id} | Deletes a Product | - | - | Token |
@ -71,7 +71,7 @@ Download the folder.
Install [Docker](https://www.docker.com/) and execute the command below
```sh
$ docker-compose up
$ docker-compose up --build
```
### Install (Manually)
@ -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)

29
_examples/database/mysql/api/api.go

@ -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)
}
}

7
_examples/database/mysql/api/category_handler.go

@ -168,6 +168,11 @@ func (h *CategoryHandler) Delete(ctx iris.Context) {
affected, err := h.service.DeleteByID(ctx.Request().Context(), id)
if err != nil {
if err == sql.ErrNoRows {
writeEntityNotFound(ctx)
return
}
debugf("CategoryHandler.Delete(DB): %v", err)
writeInternalServerError(ctx)
return
@ -201,7 +206,7 @@ func (h *CategoryHandler) ListProducts(ctx iris.Context) {
var products entity.Products
err := h.service.List(ctx.Request().Context(), &products, opts)
if err != nil {
if err != nil && err != sql.ErrNoRows {
debugf("CategoryHandler.ListProducts(DB) (table=%s where=%s=%v limit=%d offset=%d): %v",
opts.Table, opts.WhereColumn, opts.WhereValue, opts.Limit, opts.Offset, err)

2
_examples/database/mysql/main.go

@ -1,4 +1,4 @@
package main
package main // Look README.md
import (
"fmt"

121
_examples/database/mysql/migration/api_postman.json

@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "d3a2fdf6-9ebd-4e85-827d-385592a71fd6",
"_postman_id": "7b8b53f8-859a-425a-aa9c-28bc2a2d5ef7",
"name": "myapp (api-test)",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
@ -15,8 +15,9 @@
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk2MzkzNjd9.cYohwgUpe-Z7ac0LPpz4Adi5QXJmtwD1ZRpXrMUMPN0",
"type": "text"
"value": "Bearer {{token}}",
"type": "text",
"disabled": true
}
],
"body": {
@ -47,7 +48,14 @@
"name": "Get By ID",
"request": {
"method": "GET",
"header": [],
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lyaXMtZ28uY29tIiwiYXVkIjpbIjljOTY5ZDg5LTBkZGYtNDlkMC1hYzcxLTI3MmU3YmY5NjkwMCJdLCJpYXQiOjE2MDYyNzE1NDYsImV4cCI6MTYwNjI3MjQ0Nn0.l2_5iqfEaC68UySTQNoDx2sfzn031tHiTdm2kZoNkWQ",
"type": "text",
"disabled": true
}
],
"url": {
"raw": "http://localhost:8080/category/1",
"protocol": "http",
@ -71,7 +79,14 @@
},
"request": {
"method": "GET",
"header": [],
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lyaXMtZ28uY29tIiwiYXVkIjpbIjljOTY5ZDg5LTBkZGYtNDlkMC1hYzcxLTI3MmU3YmY5NjkwMCJdLCJpYXQiOjE2MDYyNzE1NDYsImV4cCI6MTYwNjI3MjQ0Nn0.l2_5iqfEaC68UySTQNoDx2sfzn031tHiTdm2kZoNkWQ",
"type": "text",
"disabled": true
}
],
"body": {
"mode": "raw",
"raw": ""
@ -113,7 +128,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"body": {
@ -150,7 +166,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"url": {
@ -177,7 +194,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"body": {
@ -185,7 +203,7 @@
"raw": "{\r\n \"title\": \"computers-technology\"\r\n}"
},
"url": {
"raw": "http://localhost:8080/category/1",
"raw": "http://localhost:8080/category/3",
"protocol": "http",
"host": [
"localhost"
@ -204,7 +222,14 @@
"name": "List Products",
"request": {
"method": "GET",
"header": [],
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lyaXMtZ28uY29tIiwiYXVkIjpbIjljOTY5ZDg5LTBkZGYtNDlkMC1hYzcxLTI3MmU3YmY5NjkwMCJdLCJpYXQiOjE2MDYyNzE1NDYsImV4cCI6MTYwNjI3MjQ0Nn0.l2_5iqfEaC68UySTQNoDx2sfzn031tHiTdm2kZoNkWQ",
"type": "text",
"disabled": true
}
],
"url": {
"raw": "http://localhost:8080/category/1/products?offset=0&limit=30&by=price&order=asc",
"protocol": "http",
@ -214,7 +239,7 @@
"port": "8080",
"path": [
"category",
"3",
"1",
"products"
],
"query": [
@ -248,7 +273,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"body": {
@ -256,7 +282,7 @@
"raw": "[{\r\n \"title\": \"product-1\",\r\n \"image_url\": \"https://images.product1.png\",\r\n \"price\": 42.42,\r\n \"description\": \"a description for product-1\"\r\n}, {\r\n \"title\": \"product-2\",\r\n \"image_url\": \"https://images.product2.png\",\r\n \"price\": 32.1,\r\n \"description\": \"a description for product-2\"\r\n}, {\r\n \"title\": \"product-3\",\r\n \"image_url\": \"https://images.product3.png\",\r\n \"price\": 52321321.32,\r\n \"description\": \"a description for product-3\"\r\n}, {\r\n \"title\": \"product-4\",\r\n \"image_url\": \"https://images.product4.png\",\r\n \"price\": 77.4221,\r\n \"description\": \"a description for product-4\"\r\n}, {\r\n \"title\": \"product-5\",\r\n \"image_url\": \"https://images.product5.png\",\r\n \"price\": 55.1,\r\n \"description\": \"a description for product-5\"\r\n}, {\r\n \"title\": \"product-6\",\r\n \"image_url\": \"https://images.product6.png\",\r\n \"price\": 53.32,\r\n \"description\": \"a description for product-6\"\r\n}]"
},
"url": {
"raw": "http://localhost:8080/category/1/products",
"raw": "http://localhost:8080/category/3/products",
"protocol": "http",
"host": [
"localhost"
@ -282,7 +308,14 @@
"name": "List",
"request": {
"method": "GET",
"header": [],
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lyaXMtZ28uY29tIiwiYXVkIjpbIjljOTY5ZDg5LTBkZGYtNDlkMC1hYzcxLTI3MmU3YmY5NjkwMCJdLCJpYXQiOjE2MDYyNzE1NDYsImV4cCI6MTYwNjI3MjQ0Nn0.l2_5iqfEaC68UySTQNoDx2sfzn031tHiTdm2kZoNkWQ",
"type": "text",
"disabled": true
}
],
"url": {
"raw": "http://localhost:8080/product?offset=0&limit=30&by=price&order=asc",
"protocol": "http",
@ -320,7 +353,14 @@
"name": "Get By ID",
"request": {
"method": "GET",
"header": [],
"header": [
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lyaXMtZ28uY29tIiwiYXVkIjpbIjljOTY5ZDg5LTBkZGYtNDlkMC1hYzcxLTI3MmU3YmY5NjkwMCJdLCJpYXQiOjE2MDYyNzE1NDYsImV4cCI6MTYwNjI3MjQ0Nn0.l2_5iqfEaC68UySTQNoDx2sfzn031tHiTdm2kZoNkWQ",
"type": "text",
"disabled": true
}
],
"url": {
"raw": "http://localhost:8080/product/1",
"protocol": "http",
@ -345,7 +385,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"url": {
@ -372,7 +413,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"body": {
@ -402,7 +444,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"body": {
@ -432,7 +475,8 @@
{
"key": "Authorization",
"value": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODk1ODU1NjN9.PtfDS1niGoZ7pV6kplI-_q1fVKLnknQ3IwcrLZhoVCU",
"type": "text"
"type": "text",
"disabled": true
}
],
"body": {
@ -480,5 +524,44 @@
"response": []
}
],
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lyaXMtZ28uY29tIiwiYXVkIjpbIjljOTY5ZDg5LTBkZGYtNDlkMC1hYzcxLTI3MmU3YmY5NjkwMCJdLCJpYXQiOjE2MDYyNzE1NDYsImV4cCI6MTYwNjI3MjQ0Nn0.l2_5iqfEaC68UySTQNoDx2sfzn031tHiTdm2kZoNkWQ",
"type": "string"
}
]
},
"event": [
{
"listen": "prerequest",
"script": {
"id": "f27c3c2d-efdc-4922-b70c-258784a1d59b",
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"id": "44d94797-9cc6-4ecd-adc5-7ad5329d79c4",
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"id": "38156b9f-e623-4974-a315-51c931670f23",
"key": "token",
"value": "token"
}
],
"protocolProfileBehavior": {}
}

6
_examples/dependency-injection/overview/web/middleware/basicauth.go

@ -5,8 +5,6 @@ package middleware
import "github.com/kataras/iris/v12/middleware/basicauth"
// BasicAuth middleware sample.
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
var BasicAuth = basicauth.Default(map[string]string{
"admin": "password",
})

6
_examples/file-server/file-server/main.go

@ -74,10 +74,8 @@ func main() {
}),
})
auth := basicauth.New(basicauth.Config{
Users: map[string]string{
"myusername": "mypassword",
},
auth := basicauth.Default(map[string]string{
"myusername": "mypassword",
})
filesRouter.Delete("/{file:path}", auth, deleteFile)

6
_examples/mvc/login/web/middleware/basicauth.go

@ -5,8 +5,6 @@ package middleware
import "github.com/kataras/iris/v12/middleware/basicauth"
// BasicAuth middleware sample.
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
var BasicAuth = basicauth.Default(map[string]string{
"admin": "password",
})

6
_examples/mvc/repository/web/middleware/basicauth.go

@ -5,8 +5,6 @@ package middleware
import "github.com/kataras/iris/v12/middleware/basicauth"
// BasicAuth middleware sample.
var BasicAuth = basicauth.New(basicauth.Config{
Users: map[string]string{
"admin": "password",
},
var BasicAuth = basicauth.Default(map[string]string{
"admin": "password",
})

23
_examples/routing/rewrite/main.go

@ -17,22 +17,21 @@ func main() {
newtest := app.Subdomain("newtest")
newtest.Get("/", newTestIndex)
newtest.Get("/", newTestAbout)
newtest.Get("/about", newTestAbout)
redirects := rewrite.Load("redirects.yml")
app.WrapRouter(redirects)
// http://mydomain.com:8080/seo/about -> http://www.mydomain.com:8080/about
// http://test.mydomain.com:8080 -> http://newtest.mydomain.com:8080
// http://test.mydomain.com:8080/seo/about -> http://newtest.mydomain.com:8080/about
// http://localhost:8080/seo -> http://localhost:8080
// http://localhost:8080/about
// http://localhost:8080/docs/v12/hello -> http://localhost:8080/docs
// http://localhost:8080/docs/v12some -> http://localhost:8080/docs
// http://localhost:8080/oldsome -> http://localhost:8080
// http://localhost:8080/oldindex/random -> http://localhost:8080
// http://localhost:8080/users.json -> http://localhost:8080/users.json
// ^ (but with an internal ?format=json, client can't see it)
// http://mydomain.com:8080/seo/about -> http://www.mydomain.com:8080/about
// http://test.mydomain.com:8080 -> http://newtest.mydomain.com:8080
// http://test.mydomain.com:8080/seo/about -> http://newtest.mydomain.com:8080/about
// http://mydomain.com:8080/seo -> http://www.mydomain.com:8080
// http://mydomain.com:8080/about
// http://mydomain.com:8080/docs/v12/hello -> http://www.mydomain.com:8080/docs
// http://mydomain.com:8080/docs/v12some -> http://www.mydomain.com:8080/docs
// http://mydomain.com:8080/oldsome -> http://www.mydomain.com:8080
// http://mydomain.com:8080/oldindex/random -> http://www.mydomain.com:8080
// http://mydomain.com:8080/users.json -> http://www.mydomain.com:8080/users?format=json
app.Listen(":8080")
}

2
_examples/routing/rewrite/redirects.yml

@ -21,4 +21,4 @@ RedirectMatch: # REDIRECT_CODE_DIGITS | PATTERN_REGEX | TARGET_REPL
# Redirects root domain to www.
# Creation of a www subdomain inside the Application is unnecessary,
# all requests are handled by the root Application itself.
PrimarySubdomain: www
PrimarySubdomain: www

10
_examples/testing/httptest/main.go

@ -8,11 +8,11 @@ import (
func newApp() *iris.Application {
app := iris.New()
authConfig := basicauth.Config{
Users: map[string]string{"myusername": "mypassword"},
opts := basicauth.Options{
Allow: basicauth.AllowUsers(map[string]string{"myusername": "mypassword"}),
}
authentication := basicauth.New(authConfig)
authentication := basicauth.New(opts) // or just: basicauth.Default(map...)
app.Get("/", func(ctx iris.Context) { ctx.Redirect("/admin") })
@ -36,7 +36,9 @@ func h(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.
// OR:
// ctx.User().GetUsername()
// ctx.User().GetPassword()
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
}

39
context/context.go

@ -1343,6 +1343,7 @@ func (ctx *Context) GetContentType() string {
// trim-ed(without the charset and priority values)
// header value of "Content-Type".
func (ctx *Context) GetContentTypeRequested() string {
// could use mime.ParseMediaType too.
return TrimHeaderValue(ctx.GetHeader(ContentTypeHeaderKey))
}
@ -1792,7 +1793,7 @@ func (ctx *Context) PostValues(name string) ([]string, error) {
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueMany(name string) (string, error) {
values, err := ctx.PostValues(name)
if err != nil {
if err != nil || len(values) == 0 {
return "", err
}
@ -1805,14 +1806,11 @@ func (ctx *Context) PostValueMany(name string) (string, error) {
// If not found then "def" is returned instead.
func (ctx *Context) PostValueDefault(name string, def string) string {
values, err := ctx.PostValues(name)
if err != nil {
if err != nil || len(values) == 0 {
return def // it returns "def" even if it's empty here.
}
if len(values) > 0 {
return values[len(values)-1]
}
return def
return values[len(values)-1]
}
// PostValue returns the last parsed form data from POST, PATCH,
@ -1835,8 +1833,8 @@ func (ctx *Context) PostValueTrim(name string) string {
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueInt(name string) (int, error) {
values, err := ctx.PostValues(name)
if err != nil {
return -1, err
if err != nil || len(values) == 0 {
return 0, err
}
return strconv.Atoi(values[len(values)-1])
@ -1860,8 +1858,8 @@ func (ctx *Context) PostValueIntDefault(name string, def int) int {
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueInt64(name string) (int64, error) {
values, err := ctx.PostValues(name)
if err != nil {
return -1, err
if err != nil || len(values) == 0 {
return 0, err
}
return strconv.ParseInt(values[len(values)-1], 10, 64)
@ -1885,8 +1883,8 @@ func (ctx *Context) PostValueInt64Default(name string, def int64) int64 {
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueFloat64(name string) (float64, error) {
values, err := ctx.PostValues(name)
if err != nil {
return -1, err
if err != nil || len(values) == 0 {
return 0, err
}
return strconv.ParseFloat(values[len(values)-1], 64)
@ -1911,7 +1909,7 @@ func (ctx *Context) PostValueFloat64Default(name string, def float64) float64 {
// See ErrEmptyForm, ErrNotFound and ErrEmptyFormField respectfully.
func (ctx *Context) PostValueBool(name string) (bool, error) {
values, err := ctx.PostValues(name)
if err != nil {
if err != nil || len(values) == 0 {
return false, err
}
@ -4726,7 +4724,7 @@ func (ctx *Context) UpsertCookie(cookie *http.Cookie, options ...CookieOption) b
// you can change it or simple, use the SetCookie for more control.
//
// See `CookieExpires` and `AddCookieOptions` for more.
var SetCookieKVExpiration = time.Duration(8760) * time.Hour
var SetCookieKVExpiration = 8760 * time.Hour
// SetCookieKV adds a cookie, requires the name(string) and the value(string).
//
@ -5343,7 +5341,15 @@ const userContextKey = "iris.user"
// SetUser sets a value as a User for this request.
// It's used by auth middlewares as a common
// method to provide user information to the
// next handlers in the chain
// next handlers in the chain.
//
// The "i" input argument can be:
// - A value which completes the User interface
// - A map[string]interface{}.
// - A value which does not complete the whole User interface
// - A value which does not complete the User interface at all
// (only its `User().GetRaw` method is available).
//
// Look the `User` method to retrieve it.
func (ctx *Context) SetUser(i interface{}) error {
if i == nil {
@ -5371,6 +5377,9 @@ func (ctx *Context) SetUser(i interface{}) error {
}
// User returns the registered User of this request.
// To get the original value (even if a value set by SetUser does not implement the User interface)
// use its GetRaw method.
// /
// See `SetUser` too.
func (ctx *Context) User() User {
if v := ctx.values.Get(userContextKey); v != nil {

86
context/context_user.go

@ -33,6 +33,8 @@ var ErrNotSupported = errors.New("not supported")
// - UserMap (a wrapper by SetUser)
// - UserPartial (a wrapper by SetUser)
type User interface {
// GetRaw should return the raw instance of the user, if supported.
GetRaw() (interface{}, error)
// GetAuthorization should return the authorization method,
// e.g. Basic Authentication.
GetAuthorization() (string, error)
@ -83,7 +85,7 @@ type SimpleUser struct {
AuthorizedAt time.Time `json:"authorized_at,omitempty"`
ID string `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"-"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
Roles []string `json:"roles,omitempty"`
Token json.RawMessage `json:"token,omitempty"`
@ -92,6 +94,11 @@ type SimpleUser struct {
var _ User = (*SimpleUser)(nil)
// GetRaw returns itself.
func (u *SimpleUser) GetRaw() (interface{}, error) {
return u, nil
}
// GetAuthorization returns the authorization method,
// e.g. Basic Authentication.
func (u *SimpleUser) GetAuthorization() (string, error) {
@ -179,6 +186,11 @@ type UserMap Map
var _ User = UserMap{}
// GetRaw returns the underline map.
func (u UserMap) GetRaw() (interface{}, error) {
return Map(u), nil
}
// GetAuthorization returns the authorization or Authorization value of the map.
func (u UserMap) GetAuthorization() (string, error) {
return u.str("authorization")
@ -292,11 +304,17 @@ type (
GetID() string
}
userGetUsername interface {
// UserGetUsername interface which
// requires a single method to complete
// a User on Context.SetUser.
UserGetUsername interface {
GetUsername() string
}
userGetPassword interface {
// UserGetPassword interface which
// requires a single method to complete
// a User on Context.SetUser.
UserGetPassword interface {
GetPassword() string
}
@ -319,78 +337,82 @@ type (
// UserPartial is a User.
// It's a helper which wraps a struct value that
// may or may not complete the whole User interface.
// See Context.SetUser.
UserPartial struct {
Raw interface{}
userGetAuthorization
userGetAuthorizedAt
userGetID
userGetUsername
userGetPassword
userGetEmail
userGetRoles
userGetToken
userGetField
Raw interface{} `json:"raw"`
userGetAuthorization `json:",omitempty"`
userGetAuthorizedAt `json:",omitempty"`
userGetID `json:",omitempty"`
UserGetUsername `json:",omitempty"`
UserGetPassword `json:",omitempty"`
userGetEmail `json:",omitempty"`
userGetRoles `json:",omitempty"`
userGetToken `json:",omitempty"`
userGetField `json:",omitempty"`
}
)
var _ User = (*UserPartial)(nil)
func newUserPartial(i interface{}) *UserPartial {
containsAtLeastOneMethod := false
if i == nil {
return nil
}
p := &UserPartial{Raw: i}
if u, ok := i.(userGetAuthorization); ok {
p.userGetAuthorization = u
containsAtLeastOneMethod = true
}
if u, ok := i.(userGetAuthorizedAt); ok {
p.userGetAuthorizedAt = u
containsAtLeastOneMethod = true
}
if u, ok := i.(userGetID); ok {
p.userGetID = u
containsAtLeastOneMethod = true
}
if u, ok := i.(userGetUsername); ok {
p.userGetUsername = u
containsAtLeastOneMethod = true
if u, ok := i.(UserGetUsername); ok {
p.UserGetUsername = u
}
if u, ok := i.(userGetPassword); ok {
p.userGetPassword = u
containsAtLeastOneMethod = true
if u, ok := i.(UserGetPassword); ok {
p.UserGetPassword = u
}
if u, ok := i.(userGetEmail); ok {
p.userGetEmail = u
containsAtLeastOneMethod = true
}
if u, ok := i.(userGetRoles); ok {
p.userGetRoles = u
containsAtLeastOneMethod = true
}
if u, ok := i.(userGetToken); ok {
p.userGetToken = u
containsAtLeastOneMethod = true
}
if u, ok := i.(userGetField); ok {
p.userGetField = u
containsAtLeastOneMethod = true
}
if !containsAtLeastOneMethod {
return nil
}
// if !containsAtLeastOneMethod {
// return nil
// }
return p
}
// GetRaw returns the original raw instance of the user.
func (u *UserPartial) GetRaw() (interface{}, error) {
if u == nil {
return nil, ErrNotSupported
}
return u.Raw, nil
}
// GetAuthorization should return the authorization method,
// e.g. Basic Authentication.
func (u *UserPartial) GetAuthorization() (string, error) {
@ -422,7 +444,7 @@ func (u *UserPartial) GetID() (string, error) {
// GetUsername should return the name of the User.
func (u *UserPartial) GetUsername() (string, error) {
if v := u.userGetUsername; v != nil {
if v := u.UserGetUsername; v != nil {
return v.GetUsername(), nil
}
@ -432,7 +454,7 @@ func (u *UserPartial) GetUsername() (string, error) {
// GetPassword should return the encoded or raw password
// (depends on the implementation) of the User.
func (u *UserPartial) GetPassword() (string, error) {
if v := u.userGetPassword; v != nil {
if v := u.UserGetPassword; v != nil {
return v.GetPassword(), nil
}

10
go.mod

@ -12,7 +12,7 @@ require (
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385
github.com/fatih/structs v1.1.0
github.com/flosch/pongo2/v4 v4.0.0
github.com/go-redis/redis/v8 v8.3.3
github.com/go-redis/redis/v8 v8.4.0
github.com/google/uuid v1.1.2
github.com/hashicorp/go-version v1.2.1
github.com/iris-contrib/httpexpect/v2 v2.0.5
@ -32,12 +32,12 @@ require (
github.com/russross/blackfriday/v2 v2.1.0
github.com/schollz/closestmatch v2.1.0+incompatible
github.com/tdewolff/minify/v2 v2.9.10
github.com/vmihailenco/msgpack/v5 v5.0.0-rc.3
github.com/vmihailenco/msgpack/v5 v5.0.0
github.com/yosssi/ace v0.0.5
go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20201027133719-8eef5233e2a1
golang.org/x/sys v0.0.0-20201028094953-708e7fb298ac
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68
golang.org/x/text v0.3.4
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/protobuf v1.25.0

642
middleware/basicauth/basicauth.go

@ -1,205 +1,547 @@
// Package basicauth provides http basic authentication via middleware. See _examples/auth/basicauth
package basicauth
/*
Test files:
- ../../_examples/auth/basicauth/main_test.go
- ./basicauth_test.go
*/
import (
"encoding/base64"
stdContext "context"
"net/http"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/sessions"
)
func init() {
context.SetHandlerName("iris/middleware/basicauth.*", "iris.basicauth")
}
const authorizationType = "Basic Authentication"
const (
// DefaultRealm is the default realm directive value on Default and Load functions.
DefaultRealm = "Authorization Required"
// DefaultMaxTriesCookie is the default cookie name to store the
// current amount of login failures when MaxTries > 0.
DefaultMaxTriesCookie = "basicmaxtries"
// DefaultCookieMaxAge is the default cookie max age on MaxTries,
// when the Options.MaxAge is zero.
DefaultCookieMaxAge = time.Hour
)
// cookieExpireDelete may be set on Cookie.Expire for expiring the given cookie.
// Note that the MaxAge is set but we set Expires field in order to support very old browsers too.
var cookieExpireDelete = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
const (
authorizationType = "Basic Authentication"
authenticateHeaderKey = "WWW-Authenticate"
proxyAuthenticateHeaderKey = "Proxy-Authenticate"
authorizationHeaderKey = "Authorization"
proxyAuthorizationHeaderKey = "Proxy-Authorization"
)
// AuthFunc accepts the current request and the username and password user inputs
// and it should optionally return a user value and report whether the login succeed or not.
// Look the Options.Allow field.
//
// Default implementations are:
// AllowUsers and AllowUsersFile functions.
type AuthFunc func(ctx *context.Context, username, password string) (interface{}, bool)
// ErrorHandler should handle the given request credentials failure.
// See Options.ErrorHandler and DefaultErrorHandler for details.
type ErrorHandler func(ctx *context.Context, err error)
// Options holds the necessary information that the BasicAuth instance needs to perform.
// The only required value is the Allow field.
//
// Usage:
// opts := Options { ... }
// auth := New(opts)
type Options struct {
// Realm directive, read http://tools.ietf.org/html/rfc2617#section-1.2 for details.
// E.g. "Authorization Required".
Realm string
// In the case of proxies, the challenging status code is 407 (Proxy Authentication Required),
// the Proxy-Authenticate response header contains at least one challenge applicable to the proxy,
// and the Proxy-Authorization request header is used for providing the credentials to the proxy server.
//
// Proxy should be used to gain access to a resource behind a proxy server.
// It authenticates the request to the proxy server, allowing it to transmit the request further.
Proxy bool
// If set to true then any non-https request will immediately
// dropped with a 505 status code (StatusHTTPVersionNotSupported) response.
//
// Defaults to false.
HTTPSOnly bool
// Allow is the only one required field for the Options type.
// Can be customized to validate a username and password combination
// and return a user object, e.g. fetch from database.
//
// There are two available builtin values, the AllowUsers and AllowUsersFile,
// both of them decode a static list of users and compares with the user input (see BCRYPT function too).
// Usage:
// - Allow: AllowUsers(iris.Map{"username": "...", "password": "...", "other_field": ...}, [BCRYPT])
// - Allow: AllowUsersFile("users.yml", [BCRYPT])
// Look the user.go source file for details.
Allow AuthFunc
// MaxAge sets expiration duration for the in-memory credentials map.
// By default an old map entry will be removed when the user visits a page.
// In order to remove old entries automatically please take a look at the `GC` option too.
//
// Usage:
// MaxAge: 30 * time.Minute
MaxAge time.Duration
// If greater than zero then the server will send 403 forbidden status code afer
// MaxTries amount of sign in failures (see MaxTriesCookie).
// Note that the client can modify the cookie and its value,
// do NOT depend for any type of custom domain logic based on this field.
// By default the server will re-ask for credentials on invalid credentials, each time.
MaxTries int
// MaxTriesCookie is the cookie name the middleware uses to
// store the failures amount on the client side.
// The lifetime of the cookie is the same as the configured MaxAge or one hour,
// therefore a forbidden client can request for authentication again after expiration.
//
// You can always set custom logic on the Allow field as you have access to the current request instance.
//
// Defaults to "basicmaxtries".
// The MaxTries should be set to greater than zero.
MaxTriesCookie string