Create auth API & comment code

This commit is contained in:
Kevin Kandlbinder 2022-02-23 18:58:46 +01:00
parent 1b15b12859
commit c8d1c33cb4
17 changed files with 475 additions and 101 deletions

View file

@ -158,19 +158,20 @@ func handleMessageEvent(matrixClient *mautrix.Client, startTs int64) mautrix.Eve
}
if content.URL != "" {
handleHashing(content, evt, matrixClient)
// This has an attachment!
handleHashing(content, evt, matrixClient) // -> handleHashing.go
return
}
// No attachment, is this a command?
if !strings.HasPrefix(content.Body, "!"+username) &&
!strings.HasPrefix(content.Body, "@"+username) &&
!(strings.HasPrefix(content.Body, username) && strings.HasPrefix(content.FormattedBody, "<a href=\"https://matrix.to/#/"+matrixClient.UserID.String()+"\">")) {
return
}
handleCommand(content.Body, evt.Sender, evt.RoomID, matrixClient)
// It is a command!
handleCommand(content.Body, evt.Sender, evt.RoomID, matrixClient) // -> commandParser.go
}
}

View file

@ -8,19 +8,19 @@ import (
"maunium.net/go/mautrix/id"
)
type StateEventPL struct {
Type string `json:"type"`
Sender string `json:"sender"`
RoomID string `json:"room_id"`
EventID string `json:"event_id"`
OriginServerTS int64 `json:"origin_server_ts"`
Content StateEventPLContent `json:"content"`
type StateEventPowerLevel struct {
Type string `json:"type"`
Sender string `json:"sender"`
RoomID string `json:"room_id"`
EventID string `json:"event_id"`
OriginServerTS int64 `json:"origin_server_ts"`
Content StateEventPowerLevelContent `json:"content"`
Unsigned struct {
Age int `json:"age"`
} `json:"unsigned"`
}
type StateEventPLContent struct {
type StateEventPowerLevelContent struct {
Ban int `json:"ban"`
Events map[string]int `json:"events"`
EventsDefault int `json:"events_default"`
@ -33,45 +33,51 @@ type StateEventPLContent struct {
UsersDefault int `json:"users_default"`
}
func GetRoomState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPLContent, error) {
// GetRoomPowerLevelState returns the rooms current power levels from the state
func GetRoomPowerLevelState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPowerLevelContent, error) {
// https://matrix.example.com/_matrix/client/r0/rooms/<roomId.String()>/state
url := matrixClient.BuildURL("rooms", roomId.String(), "state")
res, err := matrixClient.MakeRequest("GET", url, nil, nil)
if err != nil {
return nil, fmt.Errorf("ERROR: Could request room state - %v", err)
}
var stateEvents []StateEventPL
// res contains an array of state events
var stateEvents []StateEventPowerLevel
err = json.Unmarshal(res, &stateEvents)
if err != nil {
return nil, fmt.Errorf("ERROR: Could parse room state - %v", err)
}
var plEventContent StateEventPLContent
// plEventContent will hold the final event
var plEventContent StateEventPowerLevelContent
found := false
for _, e2 := range stateEvents {
if e2.Type != event.StatePowerLevels.Type {
continue
continue // If the current event is not of the power level, skip.
}
// This is what we're looking for!
found = true
plEventContent = e2.Content
break
}
if !found {
return nil, fmt.Errorf("ERROR: Could find room power level - %v", err)
}
// The following handle cases in which empty lists may have been parsed as nil
if plEventContent.Events == nil {
plEventContent.Events = make(map[string]int)
}
if plEventContent.Notifications == nil {
plEventContent.Notifications = make(map[string]int)
}
if plEventContent.Users == nil {
plEventContent.Users = make(map[string]int)
}

View file

@ -17,6 +17,7 @@ import (
"maunium.net/go/mautrix/event"
)
// handleHashing hashes and checks a message, taking configured actions on match
func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixClient *mautrix.Client) {
url, err := content.URL.Parse()
if err != nil {
@ -30,9 +31,7 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
return
}
defer func(reader io.ReadCloser) {
_ = reader.Close()
}(reader)
defer func(reader io.ReadCloser) { _ = reader.Close() }(reader)
hashWriter := sha512.New()
if _, err = io.Copy(hashWriter, reader); err != nil {
@ -42,52 +41,51 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
sum := hex.EncodeToString(hashWriter.Sum(nil))
// Fetch room configuration for adjusting behaviour
roomConfig := config.GetRoomConfig(evt.RoomID.String())
hashObj, err := db.GetEntryByHash(sum)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
/*entry := model.DBEntry{
ID: primitive.NewObjectID(),
HashValue: sum,
FileURL: "placeholder",
Timestamp: time.Now(),
AddedBy: nil,
Comments: nil,
}
db.SaveEntry(&entry)*/
if roomConfig.Debug {
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG - This file is not on the hashlist: %s", sum))
}
return
}
if roomConfig.Debug {
matrixClient.SendNotice(evt.RoomID, "DEBUG - Failed to check file. See log.")
}
fmt.Printf("Error trying to check database: %v", err)
return
}
if roomConfig.Debug {
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG !!! This file is on the hashlist: %s", sum))
jsonVal, _ := json.Marshal(hashObj)
var buf bytes.Buffer
json.Indent(&buf, jsonVal, "", " ")
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", buf.String()))
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", makeFancyJSON(jsonVal)))
}
if !checkSubscription(&roomConfig, hashObj) {
return
}
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
}
// makeFancyJSON formats / indents a JSON string
func makeFancyJSON(input []byte) string {
var buf bytes.Buffer
json.Indent(&buf, input, "", " ")
return buf.String()
}
// checkSubscription checks if the room is subscribed to one of hashObjs lists
func checkSubscription(roomConfig *config.RoomConfig, hashObj *model.DBEntry) bool {
if roomConfig.HashChecker.SubscribedLists == nil {
log.Printf("Room %s is not subscribed to any lists!", roomConfig.RoomID)
return // Not subscribed to any lists
return false // Not subscribed to any lists
}
subMap := make(map[string]bool)
@ -110,14 +108,42 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
if !found {
log.Printf("Room %s is not subscribed to any lists of hashobj %s!", roomConfig.RoomID, hashObj.ID.Hex())
return // Not subscribed
return false // Not subscribed
}
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
return true
}
// handleIllegalContent is called when a hash-match is found to take configured actions
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
switch roomConfig.HashChecker.HashCheckMode {
case 0:
postNotice(evt, matrixClient, hashObj, roomConfig)
break
case 1:
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
case 2:
muteUser(evt, matrixClient)
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
case 3:
banUser(evt, matrixClient, hashObj)
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
}
}
// redactMessage deletes the message sent in the given event
func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
opts := mautrix.ReqRedact{Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex())}
_, err := matrixClient.RedactEvent(evt.RoomID, evt.ID, opts)
@ -126,8 +152,9 @@ func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *mode
}
}
func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
plEventContent, err := GetRoomState(matrixClient, evt.RoomID)
// muteUser sets a users power-level to -1 to prevent them from sending messages
func muteUser(evt *event.Event, matrixClient *mautrix.Client) {
plEventContent, err := GetRoomPowerLevelState(matrixClient, evt.RoomID)
if err != nil {
log.Printf("ERROR: Could mute user - %v", err)
return
@ -140,9 +167,9 @@ func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBE
log.Printf("ERROR: Could mute user - %v", err)
return
}
}
// banUser bans the sender of an event from the room
func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
req := mautrix.ReqBanUser{
Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex()),
@ -152,6 +179,7 @@ func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEn
matrixClient.BanUser(evt.RoomID, &req)
}
// postNotice posts a notice about the given event into its room
func postNotice(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
local, server, err := evt.Sender.Parse()
if err != nil {
@ -172,32 +200,3 @@ If you believe this action was an accident, please contact an room administrator
SendAlert(matrixClient, evt.RoomID.String(), fmt.Sprintf(
`Veles Triggered: The message by %s (on %s) was flagged for containing material used by spammers or trolls! (Reference: %s)`, local, server, hashObj.ID.Hex()))
}*/
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
switch roomConfig.HashChecker.HashCheckMode {
case 0:
postNotice(evt, matrixClient, hashObj, roomConfig)
break
case 1:
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
case 2:
muteUser(evt, matrixClient, hashObj)
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
case 3:
banUser(evt, matrixClient, hashObj)
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
}
}

View file

@ -8,7 +8,7 @@ package bot
}
if roomConfig.AlertChannel == nil {
roomPLState, err := GetRoomState(matrixClient, id.RoomID(room))
roomPLState, err := GetRoomPowerLevelState(matrixClient, id.RoomID(room))
if err != nil {
log.Printf("Failed to get room power levels - %v", err)
return

View file

@ -35,16 +35,21 @@ type RoomConfig struct {
// Debug specifies if the bot shall run in dry run mode
Debug bool `yaml:"debug" bson:"debug"`
// AlertChannel is currently unused
AlertChannel *string `bson:"alert_channel"`
// AdminPowerLevel specifies the power-level a user has to have to manage the room
AdminPowerLevel int `bson:"admin_power_level"`
// HashChecker contains configuration specific to the hash-checker
HashChecker HashCheckerConfig `bson:"hash_checker"`
}
type HashCheckerConfig struct {
// NoticeToChat specifies weather or not to post a public notice to chat
NoticeToChat bool `bson:"chat_notice"`
// NotificationPowerLevel is currently unused
NotificationPowerLevel int `yaml:"notification_level" bson:"notification_level"`
/*
@ -58,5 +63,6 @@ type HashCheckerConfig struct {
*/
HashCheckMode uint8 `yaml:"mode" bson:"hash_check_mode"`
// SubscribedLists contains the lists this room is subscribed to
SubscribedLists []*primitive.ObjectID `bson:"subscribed_lists" json:"subscribed_lists"`
}

View file

@ -108,3 +108,51 @@ func GetListByID(id primitive.ObjectID) (*model.DBHashList, error) {
return &object, nil
}
func SaveUser(user *model.DBUser) error {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
opts := options.Replace().SetUpsert(true)
filter := bson.D{{"_id", user.ID}}
_, err := db.Collection(viper.GetString("bot.mongo.collection.users")).ReplaceOne(context.TODO(), filter, user, opts)
return err
}
func GetUserByID(id primitive.ObjectID) (*model.DBUser, error) {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
res := db.Collection(viper.GetString("bot.mongo.collection.users")).FindOne(context.TODO(), bson.D{{"_id", id}})
if res.Err() != nil {
return nil, res.Err()
}
object := model.DBUser{}
err := res.Decode(&object)
if err != nil {
return nil, err
}
return &object, nil
}
func GetUserByUsername(username string) (*model.DBUser, error) {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
res := db.Collection(viper.GetString("bot.mongo.collection.users")).FindOne(context.TODO(), bson.D{{"username", username}})
if res.Err() != nil {
return nil, res.Err()
}
object := model.DBUser{}
err := res.Decode(&object)
if err != nil {
return nil, err
}
return &object, nil
}

View file

@ -3,6 +3,6 @@ package model
import "go.mongodb.org/mongo-driver/bson/primitive"
type DBComment struct {
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"`
Content string `bson:"content" json:"content"`
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"` // CommentedBy contains a reference to the user who commented
Content string `bson:"content" json:"content"` // Content is the body of the comment
}

View file

@ -7,11 +7,11 @@ import (
type DBEntry struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Tags []string `bson:"tags" json:"tags"`
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"`
HashValue string `bson:"hash_value" json:"hash"`
FileURL string `bson:"file_url" json:"file_url"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"`
Comments []*DBComment `bson:"comments" json:"comments"`
Tags []string `bson:"tags" json:"tags"` // Tags used for searching entries and ordering
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"` // PartOf specifies the lists this entry is part of
HashValue string `bson:"hash_value" json:"hash"` // HashValue is the SHA512-hash of the file
FileURL string `bson:"file_url" json:"file_url"` // FileURL may be set to a file link
Timestamp time.Time `bson:"timestamp" json:"timestamp"` // Timestamp of when this entry was added
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"` // AddedBy is a reference to the user who added this
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this entry
}

View file

@ -4,7 +4,7 @@ import "go.mongodb.org/mongo-driver/bson/primitive"
type DBHashList struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Tags []string `bson:"tags" json:"tags"`
Comments []*DBComment `bson:"comments" json:"comments"`
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"`
Tags []string `bson:"tags" json:"tags"` // Tags of this list for discovery, and sorting
Comments []*DBComment `bson:"comments" json:"comments"` // Comments regarding this list
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"` // Maintainers contains references to the users who may edit this list
}

View file

@ -2,14 +2,20 @@ package model
import (
"encoding/base64"
"go.mongodb.org/mongo-driver/bson/primitive"
"golang.org/x/crypto/bcrypt"
)
type DBUser struct {
Username string `bson:"username" json:"username"`
HashedPassword string `bson:"password" json:"password"`
ID primitive.ObjectID `bson:"_id" json:"id"`
Password *string `bson:"-" json:"-"`
Username string `bson:"username" json:"username"` // Username is the username the user has
HashedPassword string `bson:"password" json:"password"` // HashedPassword contains the bcrypt-ed password
MatrixLinks []*string `bson:"matrix_links" json:"matrix_links"` // MatrixLinks is the matrix-users this user has verified ownership over
PendingMatrixLinks []*string `bson:"pending_matrix_links" json:"pending_matrix_links"` // PendingMatrixLinks is the matrix-users pending verification
Password *string `bson:"-" json:"-"` // Password may never be sent out!
}
func (usr *DBUser) HashPassword() error {
@ -23,6 +29,7 @@ func (usr *DBUser) HashPassword() error {
}
usr.HashedPassword = base64.StdEncoding.EncodeToString(hash)
usr.Password = nil
return nil
}

86
internal/web/api/api.go Normal file
View file

@ -0,0 +1,86 @@
package api
import (
"context"
"encoding/json"
"errors"
"github.com/gorilla/mux"
"net/http"
"strings"
)
func SetupAPI(router *mux.Router) {
router.NotFoundHandler = NotFoundHandler{}
router.MethodNotAllowedHandler = MethodNotAllowedHandler{}
router.Path("/auth/login").Methods("POST").HandlerFunc(apiHandleAuthLogin)
router.Path("/auth/register").Methods("POST").HandlerFunc(apiHandleAuthRegister)
bot := router.PathPrefix("/bot").Subrouter()
bot.Use(checkAuthMiddleware)
bot.Path("/test").HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(200)
claims := request.Context().Value("claims").(jwtClaims)
writer.Write([]byte(`hello ` + claims.Username))
})
}
func checkAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
token := req.Header.Get("Authorization")
tokenSplit := strings.Split(token, " ")
if token == "" || len(tokenSplit) < 2 {
writeJSONError(res, http.StatusUnauthorized, errors.New("bearer token required"))
return
}
token = tokenSplit[1]
claims, _, err := parseToken(token)
if err != nil {
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid token"))
return
}
ctx := context.WithValue(req.Context(), "claims", *claims)
req = req.WithContext(ctx)
next.ServeHTTP(res, req)
})
}
func writeJSONError(res http.ResponseWriter, statusCode int, err error) {
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(statusCode)
enc, _ := json.Marshal(struct {
Error string `json:"error"`
ErrorCode int `json:"error_code"`
}{
Error: err.Error(),
ErrorCode: statusCode,
})
_, _ = res.Write(enc)
}
type NotFoundHandler struct{}
func (NotFoundHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusNotFound)
_, _ = res.Write([]byte(`{"error": "not_found","error_code":404}`))
}
type MethodNotAllowedHandler struct{}
func (MethodNotAllowedHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusMethodNotAllowed)
_, _ = res.Write([]byte(`{"error": "method_not_allowed","error_code":405}`))
}

173
internal/web/api/auth.go Normal file
View file

@ -0,0 +1,173 @@
package api
import (
"encoding/json"
"errors"
"github.com/Unkn0wnCat/matrix-veles/internal/db"
"github.com/Unkn0wnCat/matrix-veles/internal/db/model"
"github.com/golang-jwt/jwt/v4"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"net/http"
"time"
)
type apiAuthRequestBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
type jwtClaims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
func parseToken(tokenString string) (*jwtClaims, *jwt.Token, error) {
claims := jwtClaims{}
jwtSigningKey := []byte(viper.GetString("bot.web.secret"))
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) {
return jwtSigningKey, nil
})
if err != nil {
return nil, nil, err
}
return &claims, token, nil
}
func apiHandleAuthLogin(res http.ResponseWriter, req *http.Request) {
body := req.Body
bodyContent := apiAuthRequestBody{}
err := json.NewDecoder(body).Decode(&bodyContent)
if err != nil {
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
return
}
user, err := db.GetUserByUsername(bodyContent.Username)
if err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid credentials"))
return
}
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
return
}
err = user.CheckPassword(bodyContent.Password)
if err != nil {
writeJSONError(res, http.StatusUnauthorized, errors.New("invalid credentials"))
return
}
jwtSigningKey := []byte(viper.GetString("bot.web.secret"))
claims := jwtClaims{
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 365 * 100)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "veles-api",
Subject: user.ID.Hex(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(jwtSigningKey)
if err != nil {
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to create token"))
return
}
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusOK)
enc, err := json.Marshal(struct {
Token string `json:"token"`
}{
Token: ss,
})
_, _ = res.Write(enc)
}
type apiAuthRegisterBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
func apiHandleAuthRegister(res http.ResponseWriter, req *http.Request) {
body := req.Body
bodyContent := apiAuthRegisterBody{}
err := json.NewDecoder(body).Decode(&bodyContent)
if err != nil {
writeJSONError(res, http.StatusBadRequest, errors.New("malformed body"))
return
}
_, err = db.GetUserByUsername(bodyContent.Username)
if err == nil {
writeJSONError(res, http.StatusBadRequest, errors.New("username taken"))
return
}
if !errors.Is(err, mongo.ErrNoDocuments) {
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
return
}
user := model.DBUser{
ID: primitive.NewObjectID(),
Username: bodyContent.Username,
Password: &bodyContent.Password,
}
err = user.HashPassword()
if err != nil {
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to hash password"))
return
}
err = db.SaveUser(&user)
if err != nil {
writeJSONError(res, http.StatusInternalServerError, errors.New("database error"))
return
}
jwtSigningKey := viper.GetString("bot.web.secret")
claims := jwtClaims{
Username: user.Username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "veles-api",
Subject: user.ID.Hex(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(jwtSigningKey)
if err != nil {
writeJSONError(res, http.StatusInternalServerError, errors.New("unable to create token"))
}
res.Header().Set("Content-Type", "application/json")
res.WriteHeader(http.StatusOK)
enc, err := json.Marshal(struct {
Token string `json:"token"`
}{
Token: ss,
})
_, _ = res.Write(enc)
}

35
internal/web/web.go Normal file
View file

@ -0,0 +1,35 @@
package web
import (
"github.com/Unkn0wnCat/matrix-veles/internal/web/api"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"log"
"net/http"
"time"
)
func StartServer() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
//r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
apiRouter := r.PathPrefix("/api").Subrouter()
api.SetupAPI(apiRouter)
srv := &http.Server{
Handler: r,
Addr: viper.GetString("bot.web.listen"),
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Printf("Now serving web-interface on http://%s", viper.GetString("bot.web.listen"))
log.Fatal(srv.ListenAndServe())
}
func HomeHandler(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(200)
writer.Write([]byte("hello world"))
}