PGP import feature
This commit is contained in:
parent
9436220685
commit
de4bb43e86
1
api.go
1
api.go
@ -21,6 +21,7 @@ func declareAPIRoutes(router *gin.Engine) {
|
|||||||
declareAPIAuthAsksRoutes(apiAuthRoutes)
|
declareAPIAuthAsksRoutes(apiAuthRoutes)
|
||||||
declareAPIAuthQuestionsRoutes(apiAuthRoutes)
|
declareAPIAuthQuestionsRoutes(apiAuthRoutes)
|
||||||
declareAPIAuthHelpRoutes(apiAuthRoutes)
|
declareAPIAuthHelpRoutes(apiAuthRoutes)
|
||||||
|
declareAPIAuthKeysRoutes(apiAuthRoutes)
|
||||||
declareAPIAuthSurveysRoutes(apiAuthRoutes)
|
declareAPIAuthSurveysRoutes(apiAuthRoutes)
|
||||||
declareAPIAuthUsersRoutes(apiAuthRoutes)
|
declareAPIAuthUsersRoutes(apiAuthRoutes)
|
||||||
declareAPIAuthWorksRoutes(apiAuthRoutes)
|
declareAPIAuthWorksRoutes(apiAuthRoutes)
|
||||||
|
12
db.go
12
db.go
@ -74,6 +74,18 @@ CREATE TABLE IF NOT EXISTS user_sessions(
|
|||||||
time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY(id_user) REFERENCES users(id_user)
|
FOREIGN KEY(id_user) REFERENCES users(id_user)
|
||||||
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS user_keys(
|
||||||
|
id_key INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
id_user INTEGER NOT NULL,
|
||||||
|
type ENUM('pgp', 'ssh') NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY(id_user) REFERENCES users(id_user)
|
||||||
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module git.nemunai.re/atsebay.t
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895
|
||||||
github.com/coreos/go-oidc/v3 v3.2.0
|
github.com/coreos/go-oidc/v3 v3.2.0
|
||||||
github.com/gin-gonic/gin v1.7.7
|
github.com/gin-gonic/gin v1.7.7
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
|
7
go.sum
7
go.sum
@ -57,7 +57,10 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@ -65,6 +68,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
|
||||||
|
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
@ -280,6 +285,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@ -448,6 +454,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
299
keys.go
Normal file
299
keys.go
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/mail"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-crypto/openpgp"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func declareAPIAuthKeysRoutes(router *gin.RouterGroup) {
|
||||||
|
router.GET("/keys", func(c *gin.Context) {
|
||||||
|
var u *User
|
||||||
|
if user, ok := c.Get("user"); ok {
|
||||||
|
u = user.(*User)
|
||||||
|
} else {
|
||||||
|
u = c.MustGet("LoggedUser").(*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err := u.GetKeys()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetKeys:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your keys. Please try again in a few moment."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []int64
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
ret = append(ret, key.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, ret)
|
||||||
|
})
|
||||||
|
router.POST("/keys", func(c *gin.Context) {
|
||||||
|
var u *User
|
||||||
|
if user, ok := c.Get("user"); ok {
|
||||||
|
u = user.(*User)
|
||||||
|
} else {
|
||||||
|
u = c.MustGet("LoggedUser").(*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
var key Key
|
||||||
|
if err := c.ShouldBindJSON(&key); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k, err := u.NewKey(key.Type, key.Content)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to NewKey:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register your public key. Please try again in a few moment."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, k)
|
||||||
|
})
|
||||||
|
|
||||||
|
keysRoutes := router.Group("/keys/:kid")
|
||||||
|
keysRoutes.Use(keyHandler)
|
||||||
|
|
||||||
|
keysRoutes.GET("", func(c *gin.Context) {
|
||||||
|
var u *User
|
||||||
|
if user, ok := c.Get("user"); ok {
|
||||||
|
u = user.(*User)
|
||||||
|
} else {
|
||||||
|
u = c.MustGet("LoggedUser").(*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := c.MustGet("key").(*Key)
|
||||||
|
|
||||||
|
if err := k.ReadInfos(u); err != nil {
|
||||||
|
log.Println("Unable to ReadInfos:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to read your public key. Please try again in a few moment."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, k)
|
||||||
|
})
|
||||||
|
keysRoutes.PUT("", func(c *gin.Context) {
|
||||||
|
current := c.MustGet("key").(*Key)
|
||||||
|
|
||||||
|
var new Key
|
||||||
|
if err := c.ShouldBindJSON(&new); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
new.Id = current.Id
|
||||||
|
|
||||||
|
u := c.MustGet("LoggedUser").(*User)
|
||||||
|
if new.IdUser != current.IdUser && !u.IsAdmin {
|
||||||
|
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Operation not allowed."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if key, err := new.Update(); err != nil {
|
||||||
|
log.Println("Unable to Update key:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during key updation: %s", err.Error())})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
keysRoutes.DELETE("", func(c *gin.Context) {
|
||||||
|
key := c.MustGet("key").(*Key)
|
||||||
|
|
||||||
|
if _, err := key.Delete(); err != nil {
|
||||||
|
log.Println("Unable to Delete key:", err)
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during key deletion: %s", err.Error())})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyHandler(c *gin.Context) {
|
||||||
|
var u *User
|
||||||
|
if user, ok := c.Get("user"); ok {
|
||||||
|
u = user.(*User)
|
||||||
|
} else {
|
||||||
|
u = c.MustGet("LoggedUser").(*User)
|
||||||
|
}
|
||||||
|
|
||||||
|
if kid, err := strconv.Atoi(string(c.Param("kid"))); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad key identifier."})
|
||||||
|
return
|
||||||
|
} else if u.IsAdmin {
|
||||||
|
if key, err := getKey(kid); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Key not found."})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.Set("key", key)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
} else if key, err := u.getKey(kid); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Key not found."})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.Set("key", key)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
IdUser int64 `json:"id_user"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Content string `json:"key,omitempty"`
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Infos map[string]interface{} `json:"infos,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetKeys() (keys []*Key, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_key, id_user, type, content, time FROM user_keys WHERE id_user=?", u.Id); errr != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var k Key
|
||||||
|
if err = rows.Scan(&k.Id, &k.IdUser, &k.Type, &k.Content, &k.Time); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keys = append(keys, &k)
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKey(id int) (k *Key, err error) {
|
||||||
|
k = new(Key)
|
||||||
|
err = DBQueryRow("SELECT id_key, id_user, type, content, time FROM user_keys WHERE id_key=?", id).Scan(&k.Id, &k.IdUser, &k.Type, &k.Content, &k.Time)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) getKey(id int) (k *Key, err error) {
|
||||||
|
k = new(Key)
|
||||||
|
err = DBQueryRow("SELECT id_key, id_user, type, content, time FROM user_keys WHERE id_key=? AND id_user=?", id, u.Id).Scan(&k.Id, &k.IdUser, &k.Type, &k.Content, &k.Time)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) NewKey(kind, content string) (*Key, error) {
|
||||||
|
if res, err := DBExec("INSERT INTO user_keys (id_user, type, content) VALUES (?, ?, ?)", u.Id, kind, content); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if kid, err := res.LastInsertId(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return &Key{kid, u.Id, kind, content, time.Now(), nil}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) CheckKey() error {
|
||||||
|
if k.Type == "pgp" {
|
||||||
|
keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(k.Content))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keys) != 1 {
|
||||||
|
return fmt.Errorf("This is not a single public key file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys[0].PrivateKey != nil {
|
||||||
|
return fmt.Errorf("You send your PRIVATE key along with your public key. YOUR PRIVATE KEY IS COMPROMISED. Please revoke and regenerate a new key pair.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if keys[0].Revocations != nil {
|
||||||
|
return fmt.Errorf("Your key seems to be revoked.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%q is not a valid key type.", k.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) ReadInfos(u *User) error {
|
||||||
|
if k.Type == "pgp" {
|
||||||
|
keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(k.Content))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
k.Content = ""
|
||||||
|
k.Infos = map[string]interface{}{}
|
||||||
|
|
||||||
|
var std_identity *openpgp.Identity
|
||||||
|
for name, idt := range keys[0].Identities {
|
||||||
|
if idt.Revoked(time.Now()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := mail.ParseAddress(name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if address.Address == u.Email {
|
||||||
|
std_identity = idt
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if std_identity.UserId != nil {
|
||||||
|
k.Infos["identity"] = std_identity.UserId.Name
|
||||||
|
k.Infos["email"] = std_identity.UserId.Email
|
||||||
|
k.Infos["comment"] = std_identity.UserId.Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
if std_identity.SelfSignature != nil {
|
||||||
|
k.Infos["keyid"] = fmt.Sprintf("%X", std_identity.SelfSignature.IssuerFingerprint)
|
||||||
|
k.Infos["sigexpired"] = std_identity.SelfSignature.SigExpired(time.Now())
|
||||||
|
k.Infos["creation"] = std_identity.SelfSignature.CreationTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%q is not a valid key type.", k.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) Update() (*Key, error) {
|
||||||
|
if _, err := DBExec("UPDATE user_keys SET id_user = ?, type = ?, content = ? WHERE id_key = ?", k.IdUser, k.Type, k.Content, k.Content, k.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return k, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k Key) Delete() (int64, error) {
|
||||||
|
if res, err := DBExec("DELETE FROM user_keys WHERE id_key = ?", k.Id); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearKeys() (int64, error) {
|
||||||
|
if res, err := DBExec("DELETE FROM user_keys"); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,7 @@ func declareStaticRoutes(router *gin.Engine) {
|
|||||||
router.GET("/bug-bounty", serveOrReverse("/"))
|
router.GET("/bug-bounty", serveOrReverse("/"))
|
||||||
router.GET("/grades", serveOrReverse("/"))
|
router.GET("/grades", serveOrReverse("/"))
|
||||||
router.GET("/help", serveOrReverse("/"))
|
router.GET("/help", serveOrReverse("/"))
|
||||||
|
router.GET("/keys", serveOrReverse("/"))
|
||||||
router.GET("/surveys", serveOrReverse("/"))
|
router.GET("/surveys", serveOrReverse("/"))
|
||||||
router.GET("/surveys/*_", serveOrReverse("/"))
|
router.GET("/surveys/*_", serveOrReverse("/"))
|
||||||
router.GET("/users", serveOrReverse("/"))
|
router.GET("/users", serveOrReverse("/"))
|
||||||
|
52
ui/src/components/UserKeys.svelte
Normal file
52
ui/src/components/UserKeys.svelte
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getKeys, getKey, Key } from '../lib/key';
|
||||||
|
|
||||||
|
export let student = null;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Informations</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#await getKeys(student.id)}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="spinner-border me-2" role="status"></div>
|
||||||
|
Chargement des clefs…
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{:then keys}
|
||||||
|
{#if keys && keys.length > 0}
|
||||||
|
{#each keys as keyid}
|
||||||
|
{#await getKey(keyid)}
|
||||||
|
Veuillez patienter
|
||||||
|
{:then key}
|
||||||
|
<tr>
|
||||||
|
<td>{key.id}</td>
|
||||||
|
<td>{key.type.toUpperCase()}</td>
|
||||||
|
<td>
|
||||||
|
<dl>
|
||||||
|
{#each Object.keys(key.infos) as k}
|
||||||
|
<dt class="float-start me-3 my-0 py-0">{k}</dt>
|
||||||
|
<dd>{#if key.infos[k]}{key.infos[k]}{:else}<span class="fst-italic">-</span>{/if}</dd>
|
||||||
|
{/each}
|
||||||
|
</dl>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/await}
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center fst-italic">Cet utilisateur n'a pas défini de clef</td>
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
61
ui/src/lib/key.js
Normal file
61
ui/src/lib/key.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
export class Key {
|
||||||
|
constructor(res) {
|
||||||
|
if (res) {
|
||||||
|
this.update(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update({ id, id_user, type, key, time, infos }) {
|
||||||
|
this.id = id;
|
||||||
|
this.id_user = id_user;
|
||||||
|
this.type = type;
|
||||||
|
this.key = key;
|
||||||
|
this.time = time;
|
||||||
|
this.infos = infos;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
const res = await fetch(`api/keys/${this.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {'Accept': 'application/json'}
|
||||||
|
});
|
||||||
|
if (res.status == 200) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
const res = await fetch(this.id?`api/keys/${this.id}`:'api/keys', {
|
||||||
|
method: this.id?'PUT':'POST',
|
||||||
|
headers: {'Accept': 'application/json'},
|
||||||
|
body: JSON.stringify(this),
|
||||||
|
});
|
||||||
|
if (res.status == 200) {
|
||||||
|
const data = await res.json();
|
||||||
|
this.update(data);
|
||||||
|
return data;
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getKeys(userid) {
|
||||||
|
const res = await fetch(userid?`api/users/${userid}/keys`:`api/keys`, {headers: {'Accept': 'application/json'}})
|
||||||
|
if (res.status == 200) {
|
||||||
|
return await res.json();
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getKey(kid) {
|
||||||
|
const res = await fetch(`api/keys/${kid}`, {headers: {'Accept': 'application/json'}})
|
||||||
|
if (res.status == 200) {
|
||||||
|
return new Key(await res.json());
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
@ -125,6 +125,7 @@
|
|||||||
<img class="rounded-circle" src="//photos.cri.epita.fr/square/{$user.login}" alt="Menu" style="margin: -0.75em 0; max-height: 2.5em; border: 2px solid white;">
|
<img class="rounded-circle" src="//photos.cri.epita.fr/square/{$user.login}" alt="Menu" style="margin: -0.75em 0; max-height: 2.5em; border: 2px solid white;">
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li><a class="dropdown-item" class:active={rroute === 'keys'} href="keys">Clef PGP</a></li>
|
||||||
<li><a class="dropdown-item" class:active={rroute === 'help'} href="help">Besoin d'aide ?</a></li>
|
<li><a class="dropdown-item" class:active={rroute === 'help'} href="help">Besoin d'aide ?</a></li>
|
||||||
<li><a class="dropdown-item" class:active={rroute === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
<li><a class="dropdown-item" class:active={rroute === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
102
ui/src/routes/keys.svelte
Normal file
102
ui/src/routes/keys.svelte
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<script>
|
||||||
|
import { getKeys, getKey, Key } from '../lib/key';
|
||||||
|
import { user } from '../stores/user';
|
||||||
|
import { ToastsStore } from '../stores/toasts';
|
||||||
|
|
||||||
|
let keysP = getKeys();
|
||||||
|
|
||||||
|
let mykey = "";
|
||||||
|
let holdSubmit = false;
|
||||||
|
|
||||||
|
async function submitPGPKey() {
|
||||||
|
holdSubmit = true;
|
||||||
|
let key = new Key({ type: 'pgp', key: mykey });
|
||||||
|
key.save().then(() => {
|
||||||
|
keysP = getKeys();
|
||||||
|
holdSubmit = false;
|
||||||
|
mykey = "";
|
||||||
|
ToastsStore.addToast({
|
||||||
|
msg: "Votre nouvelle clef a bien été enregistrée.",
|
||||||
|
color: "success",
|
||||||
|
title: "Clef PGP",
|
||||||
|
});
|
||||||
|
}, (error) => {
|
||||||
|
submitInProgress = false;
|
||||||
|
ToastsStore.addErrorToast({
|
||||||
|
msg: "Une erreur s'est produite durant l'envoi de votre clef : " + error + "\nVeuillez réessayer dans quelques instants.",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>Ma clef PGP</h2>
|
||||||
|
|
||||||
|
<p class="lead">
|
||||||
|
Vos rendus doivent être signés avec votre clef PGP.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{#await keysP}
|
||||||
|
Veuillez patienter
|
||||||
|
{:then keys}
|
||||||
|
{#if keys && keys.length > 0}
|
||||||
|
<p>
|
||||||
|
Vous avez actuellement enregistré {#if keys.length > 1}les clefs publiques suivantes{:else}la clef publique suivante{/if} :
|
||||||
|
</p>
|
||||||
|
{#each keys as keyid}
|
||||||
|
{#await getKey(keyid)}
|
||||||
|
Veuillez patienter
|
||||||
|
{:then key}
|
||||||
|
<div class="alert alert-dark d-flex justify-content-between">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="d-flex flex-column justify-content-center me-3">
|
||||||
|
<i class="bi bi-key-fill display-4"></i>
|
||||||
|
<div class="text-center badge bg-light" style="font-variant: small-caps;">
|
||||||
|
{key.type}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Adresse électronique : <strong class="badge bg-secondary">{key.infos.email}</strong><br>
|
||||||
|
Nom : <strong>{key.infos.identity}</strong> {#if key.infos.comment}<span class="fst-italic">({key.infos.identity})</span>{/if}<br>
|
||||||
|
Key ID : <strong>{key.infos.keyid}</strong><br>
|
||||||
|
Date de la signature : <strong>{key.infos.creation}</strong><br>
|
||||||
|
Clef expirée : <span class="badge" class:bg-danger={key.infos.sigexpired} class:bg-info={!key.infos.sigexpired}>{key.infos.sigexpired?"oui":"non"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column justify-content-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-danger float-end"
|
||||||
|
on:click={() => key.delete().then(() => { keysP = getKeys(); })}
|
||||||
|
>
|
||||||
|
Supprimer la clef
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
<p>
|
||||||
|
Afin de pouvoir les vérifier, veuillez envoyer votre clef publique dans le formulaire ci-dessous.
|
||||||
|
Utilisez la commande <code>gpg --export --armor {#if $user}{$user.email}{:else}login_x@epita.fr{/if}</code> :
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form class="container" on:submit|preventDefault={submitPGPKey}>
|
||||||
|
<textarea
|
||||||
|
class="form-control"
|
||||||
|
rows="10"
|
||||||
|
bind:value={mykey}
|
||||||
|
placeholder="-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
QmllbiBzw7tyIHF1ZSBjJ2VzdCB1bmUgY2hhw65uZSBxdWkgdmV1dCBkaXJlIHF1
|
||||||
|
ZWxxdWUgY2hvc2UK ...
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----"
|
||||||
|
></textarea>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="mt-2 btn btn-primary"
|
||||||
|
>
|
||||||
|
Enregistrer cette clef PGP
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
{/await}
|
@ -9,6 +9,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import UserKeys from '../../../components/UserKeys.svelte';
|
||||||
import UserSurveys from '../../../components/UserSurveys.svelte';
|
import UserSurveys from '../../../components/UserSurveys.svelte';
|
||||||
import { user } from '../../../stores/user';
|
import { user } from '../../../stores/user';
|
||||||
import { getSurveys } from '../../../lib/surveys';
|
import { getSurveys } from '../../../lib/surveys';
|
||||||
@ -102,6 +103,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">
|
||||||
|
Clefs
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<UserKeys
|
||||||
|
{student}
|
||||||
|
/>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<button
|
<button
|
||||||
class="btn btn-secondary float-end"
|
class="btn btn-secondary float-end"
|
||||||
|
1
users.go
1
users.go
@ -23,6 +23,7 @@ func declareAPIAuthUsersRoutes(router *gin.RouterGroup) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
declareAPIAuthSurveysRoutes(usersRoutes)
|
declareAPIAuthSurveysRoutes(usersRoutes)
|
||||||
|
declareAPIAuthKeysRoutes(usersRoutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
|
func declareAPIAdminUsersRoutes(router *gin.RouterGroup) {
|
||||||
|
Reference in New Issue
Block a user