1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-18 10:27:06 +00:00

add a jwt tutorial + go client

This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-11-04 21:12:13 +02:00
parent ed38047385
commit 579c3878f0
22 changed files with 818 additions and 163 deletions

View File

@@ -0,0 +1,142 @@
package api
import (
"fmt"
"os"
"time"
"myapp/domain/model"
"myapp/domain/repository"
"myapp/util"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/jwt"
)
const defaultSecretKey = "sercrethatmaycontainch@r$32chars"
func getSecretKey() string {
secret := os.Getenv(util.AppName + "_SECRET")
if secret == "" {
return defaultSecretKey
}
return secret
}
// UserClaims represents the user token claims.
type UserClaims struct {
UserID string `json:"user_id"`
Roles []model.Role `json:"roles"`
}
// Validate implements the custom struct claims validator,
// this is totally optionally and maybe unnecessary but good to know how.
func (u *UserClaims) Validate() error {
if u.UserID == "" {
return fmt.Errorf("%w: %s", jwt.ErrMissingKey, "user_id")
}
return nil
}
// Verify allows only authorized clients.
func Verify() iris.Handler {
secret := getSecretKey()
verifier := jwt.NewVerifier(jwt.HS256, []byte(secret), jwt.Expected{Issuer: util.AppName})
verifier.Extractors = []jwt.TokenExtractor{jwt.FromHeader} // extract token only from Authorization: Bearer $token
return verifier.Verify(func() interface{} {
return new(UserClaims)
})
}
// AllowAdmin allows only authorized clients with "admin" access role.
// Should be registered after Verify.
func AllowAdmin(ctx iris.Context) {
if !IsAdmin(ctx) {
ctx.StopWithText(iris.StatusForbidden, "admin access required")
return
}
ctx.Next()
}
// SignIn accepts the user form data and returns a token to authorize a client.
func SignIn(repo repository.UserRepository) iris.Handler {
secret := getSecretKey()
signer := jwt.NewSigner(jwt.HS256, []byte(secret), 15*time.Minute)
return func(ctx iris.Context) {
/*
type LoginForm struct {
Username string `form:"username"`
Password string `form:"password"`
}
and ctx.ReadForm OR use the ctx.FormValue(s) method.
*/
var (
username = ctx.FormValue("username")
password = ctx.FormValue("password")
)
user, ok := repo.GetByUsernameAndPassword(username, password)
if !ok {
ctx.StopWithText(iris.StatusBadRequest, "wrong username or password")
return
}
claims := UserClaims{
UserID: user.ID,
Roles: user.Roles,
}
// Optionally, generate a JWT ID.
jti, err := util.GenerateUUID()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
token, err := signer.Sign(claims, jwt.Claims{
ID: jti,
Issuer: util.AppName,
})
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.Write(token)
}
}
// SignOut invalidates a user from server-side using the jwt Blocklist.
// It's not used as we don't attach a blocklist, the user can be logged out from our client
// and we don't use access token so we don't actually need this in this example.
func SignOut(ctx iris.Context) {
ctx.Logout()
}
// GetClaims returns the current authorized client claims.
func GetClaims(ctx iris.Context) *UserClaims {
claims := jwt.Get(ctx).(*UserClaims)
return claims
}
// GetUserID returns the current authorized client's user id extracted from claims.
func GetUserID(ctx iris.Context) string {
return GetClaims(ctx).UserID
}
// IsAdmin reports whether the current client has admin access.
func IsAdmin(ctx iris.Context) bool {
for _, role := range GetClaims(ctx).Roles {
if role == model.Admin {
return true
}
}
return false
}

View File

@@ -0,0 +1,119 @@
package api
import (
"errors"
"myapp/domain/repository"
"github.com/kataras/iris/v12"
)
// TodoRequest represents a Todo HTTP request.
type TodoRequest struct {
Title string `json:"title" form:"title" url:"title"`
Body string `json:"body" form:"body" url:"body"`
}
// CreateTodo handles the creation of a Todo entry.
func CreateTodo(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
var req TodoRequest
err := ctx.ReadBody(&req) // will bind the "req" to a JSON, form or url query request data.
if err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
}
userID := GetUserID(ctx)
todo, err := repo.Create(userID, req.Title, req.Body)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.StatusCode(iris.StatusCreated)
ctx.JSON(todo)
}
}
// GetTodo lists all users todos.
// Parameter: {id}.
func GetTodo(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
id := ctx.Params().Get("id")
userID := GetUserID(ctx)
todo, err := repo.GetByID(id)
if err != nil {
code := iris.StatusInternalServerError
if errors.Is(err, repository.ErrNotFound) {
code = iris.StatusNotFound
}
ctx.StopWithError(code, err)
return
}
if !IsAdmin(ctx) { // admin can access any user's todos.
if todo.UserID != userID {
ctx.StopWithStatus(iris.StatusForbidden)
return
}
}
ctx.JSON(todo)
}
}
// ListTodos lists todos of the current user.
func ListTodos(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
userID := GetUserID(ctx)
todos, err := repo.GetAllByUser(userID)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
// if len(todos) == 0 {
// ctx.StopWithError(iris.StatusNotFound, fmt.Errorf("no entries found"))
// return
// }
// Or let the client decide what to do on empty list.
ctx.JSON(todos)
}
}
// ListAllTodos lists all users todos.
// Access: admin.
// Middleware: AllowAdmin.
func ListAllTodos(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
todos, err := repo.GetAll()
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.JSON(todos)
}
}
/* Leave as exercise: use filtering instead...
// ListTodosByUser lists all todos by a specific user.
// Access: admin.
// Middleware: AllowAdmin.
// Parameter: {id}.
func ListTodosByUser(repo repository.TodoRepository) iris.Handler {
return func(ctx iris.Context) {
userID := ctx.Params().Get("id")
todos, err := repo.GetAllByUser(userID)
if err != nil {
ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
ctx.JSON(todos)
}
}
*/