BIg commit to make it actualy working

main
René Jochum 1 year ago
parent 1ecc9bd640
commit c5d9147ff7
Signed by: jochum
GPG Key ID: F7D906F5E51E8E5E

@ -0,0 +1,35 @@
##########################################################
# Build
##########################################################
DOCKER_IO=docker.io
DOCKER_ORG_JO_MICRO=docker.io/jomicro
BUILD_MOUNT_FOLDER="~"
##########################################################
# Testing
##########################################################
## Micro communication
MICRO_TRANSPORT=grpc
MICRO_REGISTRY=nats
MICRO_REGISTRY_ADDRESS=localhost:4222
MICRO_BROKER=nats
MICRO_BROKER_ADDRESS=localhost:4222
## Log
MICRO_AUTH2_LOG_LEVEL=trace
## Database
MICRO_AUTH2_DATABASE_DEBUG=true
MICRO_AUTH2_DATABASE_URL="postgres://postgres:redacted@localhost:5432/auth?sslmode=disable"
MICRO_AUTH2_MIGRATIONS_DIR="./cmd/microauth2sqld/migrations"
## JWT
MICRO_AUTH2_JWT_AUDIENCE="https://lobby.wz2100.net,https://wz2100.net"
# go.micro.auth Ed25519 JWT keys in PEM - generated using '/tmp/go-build3574312808/b001/exe/microauth2sqld --auth2_generate_keys'
MICRO_AUTH2_JWT_PRIV_KEY="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSUcwQkt3elV6bnRMQXR2K1Ztb0xsYVV5ZlJBdm04SVpiY2dUMC9BZGdyekIKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo="
MICRO_AUTH2_JWT_PUB_KEY="LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQXB6V0Q5T29iWUUrMEYxbnI0MWlKL0VITC9veDZDT1NTeGlwZjh6c21IQlU9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo="
MICRO_AUTH2_JWT_REFRESH_PRIV_KEY="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSUJTVE1YTDVvUGxXWFg1azl6akpvWVVFdTJYWndkbjBvVWJRdjd6eHJIa3YKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo="
MICRO_AUTH2_JWT_REFRESH_PUB_KEY="LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQVRaWG4xWkt1Z3puTGVQdHNHUFFhbTVVS2d3K0ZCMGxudUxZYllQUnRxb1k9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo="

2
.gitignore vendored

@ -1,3 +1,5 @@
.env
.DS_STORE
.task/

@ -1,5 +1,7 @@
version: '3'
dotenv: [".env"]
vars:
GIT_TAG:
sh: git tag --points-at HEAD
@ -25,21 +27,29 @@ tasks:
volume:
run: "once"
cmds:
- podman volume inspect jo-micro_auth_go 1>/dev/null 2>&1 || podman volume create jo-micro_auth_go
- podman volume inspect jo-micro_auth2_go 1>/dev/null 2>&1 || podman volume create jo-micro_auth2_go
builder:
desc: Run something in the builder container for example "task builder -- go get -u ./..."
cmds:
- podman run --rm -v "{{.VOLUME_PATH}}:/go:rw" -v "$PWD:/code" registry.fk.jochum.dev/jo-micro/builder:latest {{.CLI_ARGS}}
- podman run --rm
-v "{{.BUILD_MOUNT_FOLDER_INT}}:{{.BUILD_MOUNT_FOLDER_INT}}"
-v "{{.PWD}}:/code"
-v "{{.VOLUME_PATH}}:/go:rw"
{{.DOCKER_ORG_JO_MICRO}}/builder:latest {{.CLI_ARGS}}
vars:
VOLUME_PATH:
sh: podman volume inspect jo-micro_auth_go --format "{{"{{"}}.Mountpoint{{"}}"}}"
sh: podman volume inspect jo-micro_auth2_go --format "{{"{{"}}.Mountpoint{{"}}"}}"
BUILD_MOUNT_FOLDER_INT:
sh: realpath {{.BUILD_MOUNT_FOLDER}}
preconditions:
- test -n "{{.CLI_ARGS}}"
protoc:
run: "once"
desc: Generate protobruf go files
sources:
- ./proto/**/*.proto
- ./internal/proto/**/*.proto
cmds:
- task: builder
vars:
@ -51,16 +61,26 @@ tasks:
sources:
- ./go.sum
- ./*.go
- ./service/microauthsqld/**/*.go
- ./service/microauth2sqld/**/*.go
- ./internal/**/*.go
cmds:
- podman build -v "{{.VOLUME_PATH}}:/go:rw" --build-arg VERSION={{.VERSION}} -t registry.fk.jochum.dev/jo-micro/auth2-sql:latest -f ./docker/microauthsqld/Dockerfile .
- podman build
-v "{{.BUILD_MOUNT_FOLDER_INT}}:{{.BUILD_MOUNT_FOLDER_INT}}"
-v "{{.VOLUME_PATH}}:/go:rw"
--build-arg VERSION={{.VERSION}}
--build-arg=DOCKER_IO={{.DOCKER_IO}}
--build-arg=DOCKER_ORG_JO_MICRO={{.DOCKER_ORG_JO_MICRO}}
-t {{.DOCKER_ORG_JO_MICRO}}/auth2-sql:latest
-f ./docker/microauth2sqld/Dockerfile
.
vars:
VOLUME_PATH:
sh: podman volume inspect jo-micro_auth_go --format "{{"{{"}}.Mountpoint{{"}}"}}"
sh: podman volume inspect jo-micro_auth2_go --format "{{"{{"}}.Mountpoint{{"}}"}}"
BUILD_MOUNT_FOLDER_INT:
sh: realpath {{.BUILD_MOUNT_FOLDER}}
podman:
desc: Generate docker container for jo-micro/auth-sql tagged as registry.fk.jochum.dev/jo-micro/auth2-sql:latest
desc: Generate docker container for jo-micro/auth-sql tagged as {{.DOCKER_ORG_JO_MICRO}}/auth2-sql:latest
cmds:
- task: build:authsql
@ -88,11 +108,11 @@ tasks:
keys:
desc: Generate keys
cmds:
- podman run registry.fk.jochum.dev/jo-micro/auth2-sql:latest microauthsqld --generate-keys
- podman run {{.DOCKER_ORG_JO_MICRO}}/auth2-sql:latest microauth2sqld --auth2_generate_keys
rm:
desc: Remove all persistent data
cmds:
- podman volume rm jo-micro_auth_go || exit 0
- podman image rm registry.fk.jochum.dev/jo-micro/auth2-sql:latest || exit 0
- podman volume rm jo-micro_auth2_go || exit 0
- podman image rm {{.DOCKER_ORG_JO_MICRO}}/auth2-sql:latest || exit 0
- rm -rf $PWD/.task

@ -0,0 +1,10 @@
package config
var (
Version = "0.0.1-dev0"
)
const (
Name = "go.micro.auth"
PkgPath = "jochum.dev/jo-micro/auth2"
)

@ -0,0 +1,18 @@
package db
import (
"context"
"fmt"
"jochum.dev/jo-micro/auth2/internal/ibun"
)
func RoleGetId(ctx context.Context, name string) (string, error) {
var result string
err := ibun.Bun.NewSelect().Table("roles").Column("id").Limit(1).Where("name = ?", name).Scan(ctx, &result)
if err != nil || len(result) < 1 {
return "", fmt.Errorf("role '%s' not found", name)
}
return result, nil
}

@ -0,0 +1,160 @@
package db
import (
"context"
"time"
"github.com/google/uuid"
"github.com/uptrace/bun"
"jochum.dev/jo-micro/auth2/internal/ibun"
)
type User struct {
bun.BaseModel `bun:"users,alias:u"`
ID uuid.UUID `bun:"id,pk,type:uuid,default:uuid_generate_v4()" json:"id" yaml:"id"`
Username string `bun:"username" json:"username" yaml:"username"`
Password string `bun:"password" json:"-" yaml:"-"`
Email string `bun:"email" json:"email" yaml:"email"`
Roles []string `bun:",array,scanonly" json:"roles" yaml:"roles"`
// Timestamps
CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp" json:"created_at" yaml:"created_at"`
UpdatedAt bun.NullTime `bun:"updated_at" json:"updated_at" yaml:"updated_at"`
// SoftDelete
DeletedAt bun.NullTime `bun:"deleted_at,soft_delete,nullzero" json:"deleted_at" yaml:"deleted_at"`
}
func UserList(ctx context.Context, limit, offset uint64) ([]User, error) {
// Get the data from the db.
var users []User
err := ibun.Bun.NewSelect().
Model(&users).
ColumnExpr("u.*").
ColumnExpr("array(SELECT r.name FROM users_roles AS ur LEFT JOIN roles AS r ON ur.role_id = r.id WHERE ur.user_id = u.id) AS roles").
Limit(int(limit)).
Offset(int(offset)).Scan(ctx)
if err != nil {
return nil, err
}
return users, nil
}
func UserDetail(ctx context.Context, id string) (*User, error) {
user := User{}
err := ibun.Bun.NewSelect().
Model(&user).
ColumnExpr("u.*").
ColumnExpr("array(SELECT r.name FROM users_roles AS ur LEFT JOIN roles AS r ON ur.role_id = r.id WHERE ur.user_id = u.id) AS roles").
Limit(1).
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, err
}
return &user, nil
}
func UserDelete(ctx context.Context, id string) error {
user := User{}
_, err := ibun.Bun.NewDelete().Model(&user).Where("id = ?", id).Exec(ctx)
return err
}
func UserUpdateRoles(ctx context.Context, id string, roles []string) (*User, error) {
// Check if all new roles exists
rolesIds := make([]string, len(roles))
for idx, role := range roles {
id, err := RoleGetId(ctx, role)
if err != nil {
return nil, err
}
rolesIds[idx] = id
}
// Delete all current roles
_, err := ibun.Bun.NewDelete().Table("users_roles").Where("user_id = ?", id).Exec(ctx)
if err != nil {
return nil, err
}
// Exit out if user wants to delete all roles
if len(roles) < 1 {
return UserDetail(ctx, id)
}
// Reassign roles
for _, roleId := range rolesIds {
values := map[string]interface{}{
"user_id": id,
"role_id": roleId,
}
_, err = ibun.Bun.NewInsert().Model(&values).TableExpr("users_roles").Exec(ctx)
if err != nil {
return nil, err
}
}
return UserDetail(ctx, id)
}
func UserFindByUsername(ctx context.Context, username string) (*User, error) {
user := User{}
err := ibun.Bun.NewSelect().
Model(&user).
ColumnExpr("u.*").
ColumnExpr("array(SELECT r.name FROM users_roles AS ur LEFT JOIN roles AS r ON ur.role_id = r.id WHERE ur.user_id = u.id) AS roles").
Limit(1).
Where("u.username = ?", username).
Scan(ctx)
if err != nil {
return nil, err
}
return &user, nil
}
func UserFindById(ctx context.Context, id string) (*User, error) {
user := User{}
err := ibun.Bun.NewSelect().
Model(&user).
ColumnExpr("u.*").
ColumnExpr("array(SELECT r.name FROM users_roles AS ur LEFT JOIN roles AS r ON ur.role_id = r.id WHERE ur.user_id = u.id) AS roles").
Limit(1).
Where("u.id = ?", id).
Scan(ctx)
if err != nil {
return nil, err
}
return &user, nil
}
func UserCreate(ctx context.Context, username, password, email string, roles []string) (*User, error) {
// Create the user
user := User{}
user.Username = username
user.Password = password
user.Email = email
_, err := ibun.Bun.NewInsert().Model(&user).Exec(ctx, &user)
if err != nil {
return nil, err
}
// Create roles
_, err = UserUpdateRoles(ctx, user.ID.String(), roles)
if err != nil {
if len(user.ID.String()) > 0 {
UserDelete(ctx, user.ID.String())
}
return nil, err
}
return &user, nil
}

@ -0,0 +1,278 @@
package handler
import (
"context"
"crypto/ed25519"
"crypto/rsa"
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v4"
"go-micro.dev/v4/errors"
"go-micro.dev/v4/util/log"
"google.golang.org/protobuf/types/known/emptypb"
"jochum.dev/jo-micro/auth2"
"jochum.dev/jo-micro/auth2/cmd/microauth2sqld/config"
"jochum.dev/jo-micro/auth2/cmd/microauth2sqld/db"
"jochum.dev/jo-micro/auth2/internal/argon2"
"jochum.dev/jo-micro/auth2/internal/proto/authpb"
"jochum.dev/jo-micro/auth2/shared/sjwt"
)
type InitConfig struct {
Audiences []string
RefreshTokenExpiry int64
AccessTokenExpiry int64
AccessTokenPubKey string
AccessTokenPrivKey string
RefreshTokenPubKey string
RefreshTokenPrivKey string
}
type Handler struct {
audiences []string
refreshTokenExpiry int64
accessTokenExpiry int64
accessTokenPubKey any
accessTokenPrivKey any
refreshTokenPubKey any
refreshTokenPrivKey any
}
func NewHandler() *Handler {
return &Handler{}
}
func (h *Handler) Init(c InitConfig) error {
h.audiences = c.Audiences
h.accessTokenExpiry = c.AccessTokenExpiry
h.refreshTokenExpiry = c.RefreshTokenExpiry
pub, priv, err := sjwt.DecodeKeyPair(c.AccessTokenPubKey, c.AccessTokenPrivKey)
if err != nil {
return err
}
h.accessTokenPubKey = pub
h.accessTokenPrivKey = priv
pub, priv, err = sjwt.DecodeKeyPair(c.RefreshTokenPubKey, c.RefreshTokenPrivKey)
if err != nil {
return err
}
h.refreshTokenPubKey = pub
h.refreshTokenPrivKey = priv
return nil
}
func (h *Handler) Stop() error {
return nil
}
func (s *Handler) List(ctx context.Context, in *authpb.ListRequest, out *authpb.UserListReply) error {
results, err := db.UserList(ctx, in.Limit, in.Offset)
if err != nil {
return err
}
// Copy the data to the result
for _, result := range results {
out.Data = append(out.Data, &authpb.User{
Id: result.ID.String(),
Username: result.Username,
Email: result.Email,
})
}
return nil
}
func (s *Handler) Detail(ctx context.Context, in *authpb.UserIDRequest, out *authpb.User) error {
result, err := db.UserDetail(ctx, in.UserId)
if err != nil {
return err
}
out.Id = result.ID.String()
out.Email = result.Email
out.Username = result.Username
out.Roles = result.Roles
return nil
}
func (s *Handler) Delete(ctx context.Context, in *authpb.UserIDRequest, out *emptypb.Empty) error {
err := db.UserDelete(ctx, in.UserId)
if err != nil {
return err
}
return nil
}
func (s *Handler) UpdateRoles(ctx context.Context, in *authpb.UpdateRolesRequest, out *authpb.User) error {
result, err := db.UserUpdateRoles(ctx, in.UserId, in.Roles)
if err != nil {
return err
}
out.Id = result.ID.String()
out.Email = result.Email
out.Username = result.Username
out.Roles = result.Roles
return nil
}
func (s *Handler) Register(ctx context.Context, in *authpb.RegisterRequest, out *authpb.User) error {
if in.Username == auth2.ROLE_SERVICE {
return errors.New(config.Name, "User already exists", http.StatusConflict)
}
hash, err := argon2.Hash(in.Password, argon2.DefaultParams)
if err != nil {
return err
}
result, err := db.UserCreate(ctx, in.Username, hash, in.Email, []string{auth2.ROLE_USER})
if err != nil {
return errors.New(config.Name, "User already exists", http.StatusConflict)
}
out.Id = result.ID.String()
out.Email = result.Email
out.Username = result.Username
out.Roles = result.Roles
return nil
}
func (s *Handler) genTokens(ctx context.Context, user *db.User, out *authpb.Token) error {
// Create the Claims
refreshClaims := sjwt.JWTClaims{
RegisteredClaims: &jwt.RegisteredClaims{
Issuer: config.Name,
Subject: user.Username,
Audience: s.audiences,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(s.accessTokenExpiry) * time.Second)),
NotBefore: jwt.NewNumericDate(time.Now()),
IssuedAt: jwt.NewNumericDate(time.Now()),
ID: user.ID.String(),
},
}
if err := refreshClaims.Valid(); err != nil {
return err
}
var (
accessToken *jwt.Token
refreshToken *jwt.Token
)
switch s.refreshTokenPrivKey.(type) {
case *rsa.PrivateKey:
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS512, refreshClaims)
case ed25519.PrivateKey:
refreshToken = jwt.NewWithClaims(jwt.SigningMethodEdDSA, refreshClaims)
}
refreshSignedToken, err := refreshToken.SignedString(s.refreshTokenPrivKey)
if err != nil {
return err
}
// Create the AccessToken
accessClaims := sjwt.JWTClaims{
RegisteredClaims: &jwt.RegisteredClaims{
Issuer: config.Name,
Subject: user.Username,
Audience: s.audiences,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(s.accessTokenExpiry) * time.Second)),
NotBefore: jwt.NewNumericDate(time.Now()),
IssuedAt: jwt.NewNumericDate(time.Now()),
ID: user.ID.String(),
},
Roles: user.Roles,
}
if err := accessClaims.Valid(); err != nil {
return err
}
switch s.accessTokenPrivKey.(type) {
case *rsa.PrivateKey:
accessToken = jwt.NewWithClaims(jwt.SigningMethodRS512, accessClaims)
case ed25519.PrivateKey:
accessToken = jwt.NewWithClaims(jwt.SigningMethodEdDSA, accessClaims)
}
accessSignedToken, err := accessToken.SignedString(s.accessTokenPrivKey)
if err != nil {
return err
}
out.RefreshToken = refreshSignedToken
out.RefreshTokenExpiresAt = refreshClaims.ExpiresAt.Unix()
out.AccessToken = accessSignedToken
out.AccessTokenExpiresAt = accessClaims.ExpiresAt.Unix()
return nil
}
func (s *Handler) Login(ctx context.Context, in *authpb.LoginRequest, out *authpb.Token) error {
user, err := db.UserFindByUsername(ctx, in.Username)
if err != nil {
log.Error(err)
return errors.New(config.Name, "Wrong username or password", http.StatusUnauthorized)
}
ok, err := argon2.Verify(in.Password, user.Password)
if err != nil {
return err
}
if !ok {
return errors.New(config.Name, "Wrong username or password", http.StatusUnauthorized)
}
return s.genTokens(ctx, user, out)
}
func (s *Handler) Refresh(ctx context.Context, in *authpb.RefreshTokenRequest, out *authpb.Token) error {
claims := sjwt.JWTClaims{}
_, err := jwt.ParseWithClaims(in.RefreshToken, &claims, func(token *jwt.Token) (interface{}, error) {
return s.refreshTokenPubKey, nil
})
if err != nil {
return errors.New(config.Name, fmt.Sprintf("checking the RefreshToken: %s", err), http.StatusBadRequest)
}
// Check claims (expiration)
if err = claims.Valid(); err != nil {
return fmt.Errorf("claims invalid: %s", err)
}
user, err := db.UserFindById(ctx, claims.ID)
if err != nil {
return errors.New(config.Name, fmt.Sprintf("error fetching the user: %s", err), http.StatusUnauthorized)
}
return s.genTokens(ctx, user, out)
}
func (s *Handler) Inspect(ctx context.Context, in *emptypb.Empty, out *authpb.JWTClaims) error {
u := ctx.Value("user")
if u == nil {
return errors.BadRequest("auth2/handler.Inspect|no user", "no user found in context")
}
u2 := u.(auth2.User)
out.Id = u2.Id
out.Type = u2.Type
out.Issuer = u2.Issuer
out.Metadata = u2.Metadata
out.Roles = u2.Roles
out.Scopes = u2.Scopes
return nil
}

@ -0,0 +1,353 @@
package main
import (
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/urfave/cli/v2"
"go-micro.dev/v4"
"go-micro.dev/v4/logger"
"jochum.dev/jo-micro/auth2"
"jochum.dev/jo-micro/auth2/cmd/microauth2sqld/config"
"jochum.dev/jo-micro/auth2/cmd/microauth2sqld/handler"
"jochum.dev/jo-micro/auth2/internal/ibun"
"jochum.dev/jo-micro/auth2/internal/ilogger"
"jochum.dev/jo-micro/auth2/internal/proto/authpb"
"jochum.dev/jo-micro/router"
_ "jochum.dev/jo-micro/auth2/plugins/client/jwt"
"jochum.dev/jo-micro/auth2/plugins/verifier/endpointroles"
)
var (
ErrorNoKeys = errors.New("config MICRO_AUTH2_JWT_*_KEY or MICRO_AUTH2_JWT_REFRESH_*_KEY not given")
)
func generateEd25519PEMKeyPair() (string, string, error) {
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return "", "", err
}
privPKCS8, err := x509.MarshalPKCS8PrivateKey(privKey)
if err != nil {
return "", "", err
}
privPem := pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: privPKCS8,
})
pubPKCS8, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return "", "", err
}
pubPem := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubPKCS8,
})
return base64.StdEncoding.EncodeToString(pubPem), base64.StdEncoding.EncodeToString(privPem), nil
}
func generateRSAPEMKeyPair(bits int) (string, string, error) {
privKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return "", "", err
}
privPKCS8, err := x509.MarshalPKCS8PrivateKey(privKey)
if err != nil {
return "", "", err
}
privPem := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privPKCS8,
})
pubPKCS8, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return "", "", err
}
pubPem := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubPKCS8,
})
return base64.StdEncoding.EncodeToString(pubPem), base64.StdEncoding.EncodeToString(privPem), nil
}
func main() {
srv := micro.NewService()
auth2ClientReg := auth2.ClientAuthRegistry()
auth2ClientReg.ForcePlugin("jwt")
flags := []cli.Flag{
// Generate
&cli.BoolFlag{
Name: "auth2_generate_keys",
Usage: "Generate keys for the config and/or the environment",
Value: false,
},
&cli.StringFlag{
Name: "auth2_generate_format",
Usage: "Format for \"auth2_generate_keys\", \"RSA4096\", \"RSA2048\" or \"Ed25519\"",
Value: "Ed25519",
},
// General
&cli.StringFlag{
Name: "auth2_sqld_router_basepath",
Usage: "Router basepath",
EnvVars: []string{"MICRO_AUTH2_SQLD_ROUTER_BASEPATH"},
Value: "auth",
},
// Keys
// given by they ClientAuth
&cli.StringFlag{
Name: "auth2_jwt_pub_key",
Usage: "Public access key PEM base64 encoded",
EnvVars: []string{"MICRO_AUTH2_JWT_PUB_KEY"},
},
&cli.StringFlag{
Name: "auth2_jwt_priv_key",
Usage: "Private access key PEM base64 encoded",
EnvVars: []string{"MICRO_AUTH2_JWT_PRIV_KEY"},
},
&cli.StringFlag{
Name: "auth2_jwt_refresh_pub_key",
Usage: "Public refresh key PEM base64 encoded",
EnvVars: []string{"MICRO_AUTH2_JWT_REFRESH_PUB_KEY"},
},
&cli.StringFlag{
Name: "auth2_jwt_refresh_priv_key",
Usage: "Private refresh key PEM base64 encoded",
EnvVars: []string{"MICRO_AUTH2_JWT_REFRESH_PRIV_KEY"},
},
// Token
&cli.Int64Flag{
Name: "auth2_jwt_refresh_expiry",
Usage: "Expire the refreshtoken after x seconds, default is one day",
EnvVars: []string{"MICRO_AUTH2_JWT_REFRESH_EXPIRY"},
Value: 86400,
},
&cli.Int64Flag{
Name: "auth2_jwt_access_expiry",
Usage: "Expire the accesstoken after x seconds, default is 15 minutes",
EnvVars: []string{"MICRO_AUTH2_JWT_ACCESS_EXPIRY"},
Value: 900,
},
&cli.StringSliceFlag{
Name: "auth2_jwt_audience",
Usage: "Add and expect this JWT audience",
EnvVars: []string{"MICRO_AUTH2_JWT_AUDIENCES"},
},
}
flags = ibun.AppendFlags(ilogger.AppendFlags(auth2ClientReg.AppendFlags(flags)))
authHandler := handler.NewHandler()
opts := []micro.Option{
micro.Name(config.Name),
micro.Version(config.Version),
micro.Flags(flags...),
micro.WrapHandler(auth2ClientReg.Plugin().Wrapper()),
micro.Action(func(c *cli.Context) error {
// Start the logger
if err := ilogger.Start(c); err != nil {
logger.Fatal(err)
return err
}
if c.Bool("auth2_generate_keys") {
var (
aPubKey string
aPrivKey string
rPubKey string
rPrivKey string
err error
)
// Just generate keys and print them to the commandline
switch c.String("auth2_generate_format") {
case "Ed25519":
aPubKey, aPrivKey, err = generateEd25519PEMKeyPair()
if err != nil {
ilogger.Logrus().Fatal(err)
}
rPubKey, rPrivKey, err = generateEd25519PEMKeyPair()
if err != nil {
ilogger.Logrus().Fatal(err)
}
case "RSA4096":
aPubKey, aPrivKey, err = generateRSAPEMKeyPair(4096)
if err != nil {
ilogger.Logrus().Fatal(err)
}
rPubKey, rPrivKey, err = generateRSAPEMKeyPair(4096)
if err != nil {
ilogger.Logrus().Fatal(err)
}
case "RSA2048":
aPubKey, aPrivKey, err = generateRSAPEMKeyPair(2048)
if err != nil {
ilogger.Logrus().Fatal(err)
}
rPubKey, rPrivKey, err = generateRSAPEMKeyPair(2048)
if err != nil {
ilogger.Logrus().Fatal(err)
}
default:
ilogger.Logrus().Fatalf("unknown key format: %s", c.String("auth2_generate_format"))
}
absPath, err := exec.LookPath(os.Args[0])
if err != nil {
// Don't fail here
absPath = os.Args[0]
}
fmt.Printf("# go.micro.auth %s JWT keys in PEM - generated using '%s %s'\n", c.String("auth2_generate_format"), absPath, strings.Join(os.Args[1:len(os.Args)], " "))
fmt.Printf("MICRO_AUTH2_JWT_PRIV_KEY=\"%s\"\n", aPrivKey)
fmt.Printf("MICRO_AUTH2_JWT_PUB_KEY=\"%s\"\n", aPubKey)
fmt.Printf("MICRO_AUTH2_JWT_REFRESH_PRIV_KEY=\"%s\"\n", rPrivKey)
fmt.Printf("MICRO_AUTH2_JWT_REFRESH_PUB_KEY=\"%s\"\n", rPubKey)
os.Exit(0)
}
if err := auth2ClientReg.Init(c, srv); err != nil {
ilogger.Logrus().Fatal(err)
}
authVerifier := endpointroles.NewVerifier()
authVerifier.AddRules(
endpointroles.RouterRule,
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.Delete),
endpointroles.RolesAllow(auth2.RolesServiceAndAdmin),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.Detail),
endpointroles.RolesAllow(auth2.RolesServiceAndUsersAndAdmin),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.Inspect),
endpointroles.RolesAllow(auth2.RolesServiceAndUsersAndAdmin),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.List),
endpointroles.RolesAllow(auth2.RolesServiceAndAdmin),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.Login),
endpointroles.RolesAllow(auth2.RolesAllAndAnon),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.Refresh),
endpointroles.RolesAllow(auth2.RolesUsersAndAdmin),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.Register),
endpointroles.RolesAllow(auth2.RolesAllAndAnon),
),
endpointroles.NewRule(
endpointroles.Endpoint(authpb.AuthService.UpdateRoles),
endpointroles.RolesAllow(auth2.RolesAdmin),
),
)
auth2ClientReg.Plugin().SetVerifier(authVerifier)
// Connect to the database
if err := ibun.Start(c); err != nil {
ilogger.Logrus().Fatal(err)
}
// Check if we got keys
if c.String("auth2_jwt_pub_key") == "" || c.String("auth2_jwt_priv_key") == "" || c.String("auth2_jwt_refresh_pub_key") == "" || c.String("auth2_jwt_refresh_priv_key") == "" {
ilogger.Logrus().Fatal(ErrorNoKeys)
}
// Check the other handler cli arguments
if c.Int64("auth2_jwt_access_expiry") < 1 {
ilogger.Logrus().Fatal(errors.New("MICRO_AUTH2_JWT_ACCESS_EXPIRY must be great than 0"))
}
if c.Int64("auth2_jwt_refresh_expiry") < 1 {
ilogger.Logrus().Fatal(errors.New("MICRO_AUTH2_JWT_REFRESH_EXPIRY must be great than 0"))
}
if c.StringSlice("auth2_jwt_audience") == nil {
ilogger.Logrus().Fatal(errors.New("MICRO_AUTH2_JWT_AUDIENCES must be given"))
}
if err := authHandler.Init(handler.InitConfig{
Audiences: c.StringSlice("auth2_jwt_audience"),
RefreshTokenExpiry: c.Int64("auth2_jwt_refresh_expiry"),
AccessTokenExpiry: c.Int64("auth2_jwt_access_expiry"),
AccessTokenPubKey: c.String("auth2_jwt_pub_key"),
AccessTokenPrivKey: c.String("auth2_jwt_priv_key"),
RefreshTokenPubKey: c.String("auth2_jwt_refresh_pub_key"),
RefreshTokenPrivKey: c.String("auth2_jwt_refresh_priv_key"),
}); err != nil {
ilogger.Logrus().Fatal(err)
}
authpb.RegisterAuthServiceHandler(srv.Server(), authHandler)
// Register with https://jochum.dev/jo-micro/router
r := router.NewHandler(
c.String("auth2_sqld_router_basepath"),
router.NewRoute(
router.Method(router.MethodGet),
router.Path("/routes"),
router.Endpoint("routes"),
),
)
r.RegisterWithServer(srv.Server())
return nil
}),
}
srv.Init(opts...)
// Run server
if err := srv.Run(); err != nil {
ilogger.Logrus().Fatal(err)
}
// Disconnect from the database
if err := ibun.Stop(); err != nil {
ilogger.Logrus().Fatal(err)
}
// Stop the auth Plugin
if err := auth2ClientReg.Stop(); err != nil {
ilogger.Logrus().Fatal(err)
}
// Stop the logger
if err := ilogger.Stop(); err != nil {
logger.Fatal(err)
}
}

@ -0,0 +1,9 @@
BEGIN;
DROP TABLE IF EXISTS public.users;
DROP TABLE IF EXISTS public.roles;
DROP TABLE IF EXISTS public.users_roles;
DROP TABLE IF EXISTS public.tokens;
DROP TABLE IF EXISTS public.users_tokens;
COMMIT;

@ -0,0 +1,44 @@
BEGIN;
CREATE TABLE public.roles
(
id bigserial PRIMARY KEY,
name varchar(32) COLLATE pg_catalog."default" NOT NULL,
created_at TIMESTAMPTZ DEFAULT Now() NOT NULL,
updated_at TIMESTAMPTZ NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE UNIQUE INDEX name_idx ON public.roles (name) WHERE (deleted_at IS NULL);
CREATE TABLE public.users
(
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username varchar(255) COLLATE pg_catalog."default" NOT NULL,
password varchar(255) COLLATE pg_catalog."default" NOT NULL,
email varchar(255) COLLATE pg_catalog."default" NOT NULL,
created_at TIMESTAMPTZ DEFAULT Now() NOT NULL,
updated_at TIMESTAMPTZ NULL,
deleted_at TIMESTAMPTZ NULL
);
CREATE UNIQUE INDEX username_idx ON public.users (username) WHERE (deleted_at IS NULL);
CREATE UNIQUE INDEX email_idx ON public.users (email) WHERE (deleted_at IS NULL);
CREATE TABLE public.users_roles
(
user_id UUID NOT NULL,
role_id BIGINT NOT NULL,
UNIQUE(role_id, user_id),
FOREIGN KEY(user_id) REFERENCES public.users(id) ON DELETE CASCADE,
FOREIGN KEY(role_id) REFERENCES public.roles(id) ON DELETE CASCADE
);
CREATE INDEX user_id_idx ON users_roles (user_id);
CREATE INDEX role_id_idx ON users_roles (role_id);
INSERT INTO roles (name) VALUES ('service'), ('user'), ('admin'), ('superadmin');
INSERT INTO users (id, username, password, email) VALUES ('2e4d8ed5-934d-4cd2-84fb-bd650d3a1ded', 'admin', '$argon2id$v=19$m=131072,t=4,p=4$sMaZvvQn2uWrISQICSbBqQ$L9tNlTTs4ldx0Ry+8Ctu8trSN27Q5iY68iWLjtprOfY', 'admin@wz2100.net');
INSERT INTO users_roles (user_id, role_id) VALUES ('2e4d8ed5-934d-4cd2-84fb-bd650d3a1ded', 2), ('2e4d8ed5-934d-4cd2-84fb-bd650d3a1ded', 3), ('2e4d8ed5-934d-4cd2-84fb-bd650d3a1ded', 4);
COMMIT;

@ -1,71 +0,0 @@
package config
var (
Version = "0.0.1-dev0"
)
const (
Name = "go.micro.auth"
PkgPath = "jochum.dev/jo-micro/auth2"
)
const (
EnvDev = "dev"
EnvProd = "prod"
)
type Config struct {
Auth AuthConfig
}
type ServerConfig struct {
Env string
RouterURI string
}
type TokenKeys struct {
PrivKey string
PubKey string
}
type AuthConfig struct {
Server ServerConfig
RefreshTokenExpiry int
AccessTokenExpiry int
AccessToken TokenKeys
RefreshToken TokenKeys
}
func GetConfig() *Config {
return &_cfg
}
func GetServerConfig() ServerConfig {
return _cfg.Auth.Server
}
func GetAuthConfig() AuthConfig {
return _cfg.Auth
}
// internal instance of Config with defaults
var _cfg = Config{
Auth: AuthConfig{
Server: ServerConfig{
Env: EnvProd,
RouterURI: "auth",
},
RefreshTokenExpiry: 86400 * 14, // 14 days
AccessTokenExpiry: 900, // 15 minutes
AccessToken: TokenKeys{
PrivKey: "",
PubKey: "",
},
RefreshToken: TokenKeys{
PrivKey: "",
PubKey: "",
},
},
}

@ -1,129 +0,0 @@
package main
import (
"crypto/ed25519"
"encoding/base64"
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/urfave/cli/v2"
"go-micro.dev/v4"
"go-micro.dev/v4/logger"
"jochum.dev/jo-micro/auth2/cmd/microauthsqld/config"
iconfig "jochum.dev/jo-micro/auth2/internal/config"
iLogger "jochum.dev/jo-micro/auth2/internal/logger"
"jochum.dev/jo-micro/router"
"jochum.dev/jo-micro/auth2/internal/bun"
)
var (
ErrorNoKeys = errors.New("config AUTH_ACCESS_TOKEN_*_KEY or AUTH_REFRESH_TOKEN_*_KEY not given")
)
func main() {
if err := iconfig.Load(config.GetConfig()); err != nil {
logger.Fatal(err)
}
srv := micro.NewService()
flags := []cli.Flag{
&cli.BoolFlag{
Name: "generate-keys",
Usage: "Generate keys for the config and/or the environment",
Value: false,
},
}
flags = append(flags, bun.Flags()...)
flags = append(flags, iLogger.Flags()...)
opts := []micro.Option{
micro.Name(config.Name),
micro.Version(config.Version),
micro.Flags(flags...),
micro.Action(func(c *cli.Context) error {
if c.Bool("generate-keys") {
// Just generate keys and print them to the commandline
aPubKeyB, aPrivKeyB, err := ed25519.GenerateKey(nil)
if err != nil {
log.Fatal(err)
return err
}
rPubKeyB, rPrivKeyB, err := ed25519.GenerateKey(nil)
if err != nil {
log.Fatal(err)
return err
}
absPath, err := exec.LookPath(os.Args[0])
if err != nil {
// Don't fail here
absPath = os.Args[0]
}
fmt.Printf("# go.micro.auth ed25519 JWT keys - generated using '%s %s'\n", absPath, strings.Join(os.Args[1:len(os.Args)], " "))
fmt.Printf("AUTH_ACCESSTOKEN_PRIVKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(aPrivKeyB))
fmt.Printf("AUTH_ACCESSTOKEN_PUBKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(aPubKeyB))
fmt.Printf("AUTH_REFRESHTOKEN_PRIVKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(rPrivKeyB))
fmt.Printf("AUTH_REFRESHTOKEN_PUBKEY=\"%s\"\n", base64.StdEncoding.EncodeToString(rPubKeyB))
os.Exit(0)
}
// Start the logger
if err := iLogger.Start(c); err != nil {
log.Fatal(err)
return err
}
// Connect to the database
if err := bun.Start(c); err != nil {
log.Fatal(err)
return err
}
// Check if we got keys
authConfig := config.GetAuthConfig()
if authConfig.AccessToken.PrivKey == "" || authConfig.AccessToken.PubKey == "" || authConfig.RefreshToken.PrivKey == "" || authConfig.RefreshToken.PubKey == "" {
log.Fatal(ErrorNoKeys)
return ErrorNoKeys
}
// Register with https://jochum.dev/jo-micro/router
r := router.NewHandler(
config.GetServerConfig().RouterURI,
router.NewRoute(
router.Method(router.MethodGet),
router.Path("/routes"),
router.Endpoint("routes"),
),
)
r.RegisterWithServer(srv.Server())
return nil
}),
}
srv.Init(opts...)
// Run server
if err := srv.Run(); err != nil {
logger.Fatal(err)
}
// Disconnect from the database
if err := bun.Stop(); err != nil {
logger.Fatal(err)
}
// Stop the logger
if err := iLogger.Stop(); err != nil {
logger.Fatal(err)
}
}

@ -1,5 +1,8 @@
ARG DOCKER_IO=docker.io
ARG DOCKER_ORG_JO_MICRO=docker.io/jomicro
# STEP 1 build executable binary
FROM registry.fk.jochum.dev/jo-micro/builder:latest AS builder
FROM ${DOCKER_ORG_JO_MICRO}/builder:latest AS builder
# Create appuser (/etc/passwd entry for the runner container)
RUN useradd appuser
@ -7,23 +10,23 @@ RUN useradd appuser
ARG VERSION
COPY . /code/
WORKDIR /code
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -installsuffix cgo -ldflags="-w -s -X 'jochum.dev/jo-micro/auth2/cmd/microauthsqld/config.Version=$VERSION'" -o /usr/local/bin/microauthsqld jochum.dev/jo-micro/auth2/cmd/microauthsqld
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -installsuffix cgo -ldflags="-w -s -X 'jochum.dev/jo-micro/auth2/cmd/microauth2sqld/config.Version=$VERSION'" -o /usr/local/bin/microauth2sqld jochum.dev/jo-micro/auth2/cmd/microauth2sqld
# STEP 2 build a small image
# start from busybox
FROM busybox
FROM ${DOCKER_IO}/library/busybox:latest
LABEL maintainer="René Jochum <rene@jochum.dev>"
# Copy certs, passwd and binary from builder
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /usr/local/bin/microauthsqld /usr/local/bin/microauthsqld
RUN chmod +x /usr/local/bin/microauthsqld
COPY --from=builder /usr/local/bin/microauth2sqld /usr/local/bin/microauth2sqld
RUN chmod +x /usr/local/bin/microauth2sqld
COPY ./cmd/microauthsqld/migrations /migrations
COPY ./cmd/microauth2sqld/migrations /migrations
# Run as appuser
USER appuser
CMD [ "/usr/local/bin/microauthsqld" ]
CMD [ "/usr/local/bin/microauth2sqld" ]

@ -0,0 +1,19 @@
#!/bin/sh
# From: https://stackoverflow.com/a/20909045
## Usage:
## . ./env.sh ; $COMMAND
## . ./env.sh ; echo ${MINIENTREGA_FECHALIMITE}
unamestr=$(uname)
if [ "$unamestr" = 'Linux' ]; then
export $(grep -v '^#' .env | xargs -d '\n')
elif [ "$unamestr" = 'FreeBSD' ] || [ "$unamestr" = 'Darwin' ]; then
export $(grep -v '^#' .env | xargs -0)
fi

@ -5,75 +5,61 @@ go 1.19
require (
github.com/avast/retry-go v3.0.0+incompatible
github.com/go-micro/plugins/v4/broker/nats v1.1.1-0.20220908125827-e0369dde429b
github.com/go-micro/plugins/v4/config/encoder/toml v1.1.0
github.com/go-micro/plugins/v4/config/encoder/yaml v1.1.0
github.com/go-micro/plugins/v4/logger/logrus v1.1.0
github.com/go-micro/plugins/v4/registry/nats v1.1.1-0.20220908125827-e0369dde429b
github.com/go-micro/plugins/v4/transport/grpc v1.1.0
github.com/go-micro/plugins/v4/transport/nats v1.1.1-0.20220908125827-e0369dde429b
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/golang-migrate/migrate v3.5.4+incompatible
github.com/golang-migrate/migrate/v4 v4.15.2
github.com/google/uuid v1.3.0
github.com/jackc/pgx v3.6.2+incompatible
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
github.com/uptrace/bun v1.1.8
github.com/uptrace/bun/dialect/pgdialect v1.1.8
github.com/uptrace/bun/extra/bundebug v1.1.8
github.com/urfave/cli/v2 v2.16.3
go-micro.dev/v4 v4.8.1
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
google.golang.org/protobuf v1.28.1
jochum.dev/jo-micro/router v0.2.3
jochum.dev/jo-micro/router v0.2.4
)
require (
github.com/BurntSushi/toml v1.2.0 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect
github.com/cloudflare/circl v1.2.0 // indirect
github.com/cockroachdb/apd v1.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.8.1 // indirect
github.com/go-acme/lego/v4 v4.8.0 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/go-git/go-git/v5 v5.4.2 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.1.0 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.15.10 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/miekg/dns v1.1.50 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.3.0 // indirect
github.com/nats-io/nats.go v1.17.0 // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
@ -81,26 +67,26 @@ require (
github.com/nxadm/tail v1.4.8 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.2 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/net v0.0.0-20220920203100-d0c6ba3f52d9 // indirect
golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41 // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa // indirect
google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect
google.golang.org/grpc v1.49.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

1756
go.sum

File diff suppressed because it is too large Load Diff

@ -0,0 +1,115 @@
// From: https://www.alexedwards.net/blog/how-to-hash-and-verify-passwords-with-argon2-in-go
package argon2
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"errors"
"fmt"
"strings"
"golang.org/x/crypto/argon2"
)
type Params struct {
memory uint32
iterations uint32
parallelism uint8
saltLength uint32
keyLength uint32
}
var DefaultParams = &Params{
memory: 128 * 1024,
iterations: 4,
parallelism: 4,
saltLength: 16,
keyLength: 32,
}
func Hash(password string, p *Params) (encodedHash string, err error) {
salt, err := generateRandomBytes(p.saltLength)
if err != nil {
return "", err
}
hash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
// Base64 encode the salt and hashed password.
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
// Return a string using the standard encoded hash representation.
encodedHash = fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, p.memory, p.iterations, p.parallelism, b64Salt, b64Hash)
return encodedHash, nil
}
func Verify(password string, encodedHash string) (bool, error) {
// Extract the parameters, salt and derived key from the encoded password
// hash.
p, salt, hash, err := decodeHash(encodedHash)
if err != nil {
return false, err
}
// Derive the key from the other password using the same parameters.
otherHash := argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength)
// Check that the contents of the hashed passwords are identical. Note