Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth #68

Merged
merged 15 commits into from
Jan 1, 2024
Merged

Auth #68

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion docs/swagger/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,25 @@ const docTemplate = `{
}
}
},
"/login": {
"post": {
"tags": [
"General"
],
"summary": "Login user",
"responses": {
"302": {
"description": "Found"
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/repo": {
"get": {
"produces": [
Expand Down Expand Up @@ -399,6 +418,25 @@ const docTemplate = `{
}
}
}
},
"/user": {
"get": {
"tags": [
"User"
],
"summary": "Get username of current logged-in user",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized"
}
}
}
}
},
"definitions": {
Expand Down Expand Up @@ -548,7 +586,7 @@ const docTemplate = `{

// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "0.5",
Version: "0.6",
Host: "localhost:11771",
BasePath: "/api",
Schemes: []string{"http"},
Expand Down
40 changes: 39 additions & 1 deletion docs/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"name": "Apache 2.0",
"url": "https://github.com/meltred/meltcd/blob/main/LICENSE"
},
"version": "0.5"
"version": "0.6"
},
"host": "localhost:11771",
"basePath": "/api",
Expand Down Expand Up @@ -249,6 +249,25 @@
}
}
},
"/login": {
"post": {
"tags": [
"General"
],
"summary": "Login user",
"responses": {
"302": {
"description": "Found"
},
"400": {
"description": "Bad Request"
},
"401": {
"description": "Unauthorized"
}
}
}
},
"/repo": {
"get": {
"produces": [
Expand Down Expand Up @@ -396,6 +415,25 @@
}
}
}
},
"/user": {
"get": {
"tags": [
"User"
],
"summary": "Get username of current logged-in user",
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "string"
}
},
"401": {
"description": "Unauthorized"
}
}
}
}
},
"definitions": {
Expand Down
26 changes: 25 additions & 1 deletion docs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ info:
name: Apache 2.0
url: https://github.com/meltred/meltcd/blob/main/LICENSE
title: Meltcd API
version: "0.5"
version: "0.6"
paths:
/:
get:
Expand Down Expand Up @@ -255,6 +255,18 @@ paths:
summary: Refresh/Synchronize an application
tags:
- Apps
/login:
post:
responses:
"302":
description: Found
"400":
description: Bad Request
"401":
description: Unauthorized
summary: Login user
tags:
- General
/repo:
delete:
consumes:
Expand Down Expand Up @@ -351,6 +363,18 @@ paths:
summary: Update a repository
tags:
- Repo
/user:
get:
responses:
"200":
description: OK
schema:
type: string
"401":
description: Unauthorized
summary: Get username of current logged-in user
tags:
- User
schemes:
- http
swagger: "2.0"
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ require (
github.com/sergi/go-diff v1.3.1 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/crypto v0.17.0
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/time v0.4.0 // indirect
Expand Down
80 changes: 80 additions & 0 deletions internal/core/auth/password/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package password

import (
"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 (
ErrInvalidHash = errors.New("the encoded hash is not in the correct format")
ErrIncompatibleVersion = errors.New("incompatible version of argon2")
)

func ComparePasswordAndHash(password, encodedHash string) (match bool, err 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
// that we are using the subtle.ConstantTimeCompare() function for this
// to help prevent timing attacks.
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
return true, nil
}
return false, nil
}

func decodeHash(encodedHash string) (p *Params, salt, hash []byte, err error) {
vals := strings.Split(encodedHash, "$")
if len(vals) != 6 {
return nil, nil, nil, ErrInvalidHash
}

var version int
_, err = fmt.Sscanf(vals[2], "v=%d", &version)
if err != nil {
return nil, nil, nil, err
}
if version != argon2.Version {
return nil, nil, nil, ErrIncompatibleVersion
}

p = &Params{}
_, err = fmt.Sscanf(vals[3], "m=%d,t=%d,p=%d", &p.Memory, &p.Iterations, &p.Parallelism)
if err != nil {
return nil, nil, nil, err
}

salt, err = base64.RawStdEncoding.Strict().DecodeString(vals[4])
if err != nil {
return nil, nil, nil, err
}
p.SaltLength = uint32(len(salt))

hash, err = base64.RawStdEncoding.Strict().DecodeString(vals[5])
if err != nil {
return nil, nil, nil, err
}
p.KeyLength = uint32(len(hash))

return p, salt, hash, nil
}
37 changes: 37 additions & 0 deletions internal/core/auth/password/encode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package password

import (
"crypto/rand"
"encoding/base64"
"fmt"

"golang.org/x/crypto/argon2"
)

func GenerateFromPassword(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 generateRandomBytes(n uint32) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}

return b, nil
}
49 changes: 49 additions & 0 deletions internal/core/auth/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package auth

import (
"time"
)

type Session struct {
Token string `json:"authToken"`
Username string `json:"username"`
ExpireTime time.Time `json:"expireTime"`
}

var sessions []*Session

func AddSession(token, username string, expireTime time.Time) {
sessions = append(sessions, &Session{
Token: token,
Username: username,
ExpireTime: expireTime,
})
}

func RemoveSession(token string) {
var result []*Session

for _, ses := range sessions {
if ses.Token != token {
result = append(result, ses)
}
}

sessions = result
}

func VerifySession(token string) (string, bool) {
for _, ses := range sessions {
if ses.Token == token {
if time.Now().After(ses.ExpireTime) {
defer RemoveSession(token)

return "", false
}

return ses.Username, true
}
}

return "", false
}
Loading