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)
|
||||
declareAPIAuthQuestionsRoutes(apiAuthRoutes)
|
||||
declareAPIAuthHelpRoutes(apiAuthRoutes)
|
||||
declareAPIAuthKeysRoutes(apiAuthRoutes)
|
||||
declareAPIAuthSurveysRoutes(apiAuthRoutes)
|
||||
declareAPIAuthUsersRoutes(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,
|
||||
FOREIGN KEY(id_user) REFERENCES users(id_user)
|
||||
) 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 {
|
||||
return err
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -3,6 +3,7 @@ module git.nemunai.re/atsebay.t
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
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/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/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/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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
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/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
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-20200629203442-efcf912fb354/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-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-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/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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-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-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-20211210111614-af8b64212486/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("/grades", serveOrReverse("/"))
|
||||
router.GET("/help", serveOrReverse("/"))
|
||||
router.GET("/keys", serveOrReverse("/"))
|
||||
router.GET("/surveys", serveOrReverse("/"))
|
||||
router.GET("/surveys/*_", 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;">
|
||||
</a>
|
||||
<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 === 'bug-bounty'} href="bug-bounty">Bug Bounty</a></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 lang="ts">
|
||||
import UserKeys from '../../../components/UserKeys.svelte';
|
||||
import UserSurveys from '../../../components/UserSurveys.svelte';
|
||||
import { user } from '../../../stores/user';
|
||||
import { getSurveys } from '../../../lib/surveys';
|
||||
@ -102,6 +103,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">
|
||||
Clefs
|
||||
</h3>
|
||||
</div>
|
||||
<UserKeys
|
||||
{student}
|
||||
/>
|
||||
<div class="card-header">
|
||||
<button
|
||||
class="btn btn-secondary float-end"
|
||||
|
Reference in New Issue
Block a user