755 lines
23 KiB
Go
755 lines
23 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"git.ailur.dev/ailur/burgernotes/git.ailur.dev/ailur/burgernotes/protobuf"
|
|
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"crypto/ed25519"
|
|
"encoding/json"
|
|
"io/fs"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
library "git.ailur.dev/ailur/fg-library/v2"
|
|
nucleusLibrary "git.ailur.dev/ailur/fg-nucleus-library"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/google/uuid"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
var ServiceInformation = library.Service{
|
|
Name: "burgernotes",
|
|
Permissions: library.Permissions{
|
|
Authenticate: true, // This service does require authentication
|
|
Database: true, // This service does require database access
|
|
BlobStorage: true, // This service does require blob storage access
|
|
InterServiceCommunication: true, // This service does require inter-service communication
|
|
Resources: false, // This service is API-only, so it does not require resources
|
|
},
|
|
ServiceID: uuid.MustParse("b0bee29e-00c4-4ead-a5d6-3f792ff25174"),
|
|
}
|
|
|
|
func unmarshalProtobuf(r *http.Request, protobuf proto.Message) error {
|
|
var protobufData []byte
|
|
_, err := r.Body.Read(protobufData)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = proto.Unmarshal(protobufData, protobuf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
|
|
// Log the message to the logger service
|
|
information.Outbox <- library.InterServiceMessage{
|
|
ServiceID: ServiceInformation.ServiceID,
|
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000002"), // Logger service
|
|
MessageType: messageType,
|
|
SentAt: time.Now(),
|
|
Message: message,
|
|
}
|
|
}
|
|
|
|
func askBlobService(body any, information library.ServiceInitializationInformation, context uint64) (library.InterServiceMessage, error) {
|
|
// Ask the blob storage service for the thing
|
|
information.Outbox <- library.InterServiceMessage{
|
|
ServiceID: ServiceInformation.ServiceID,
|
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000003"), // Blob storage service
|
|
MessageType: context,
|
|
SentAt: time.Now(),
|
|
Message: body,
|
|
}
|
|
|
|
// 3 second timeout
|
|
timeoutChan := make(chan struct{})
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
logFunc("Timeout while waiting for the quota from the blob storage service", 2, information)
|
|
close(timeoutChan)
|
|
}()
|
|
|
|
// Wait for the response
|
|
select {
|
|
case response := <-information.Inbox:
|
|
return response, nil
|
|
case <-timeoutChan:
|
|
return library.InterServiceMessage{}, errors.New("timeout")
|
|
}
|
|
}
|
|
|
|
func getQuotaOrUsed(userID uuid.UUID, information library.ServiceInitializationInformation, context uint64) (int64, error) {
|
|
response, err := askBlobService(userID, information, context)
|
|
if err != nil {
|
|
return 0, err
|
|
} else if response.MessageType != 0 {
|
|
return 0, response.Message.(error)
|
|
} else {
|
|
return response.Message.(int64), nil
|
|
}
|
|
}
|
|
|
|
func getQuota(userID uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
|
return getQuotaOrUsed(userID, information, 3)
|
|
}
|
|
|
|
func getUsed(userID uuid.UUID, information library.ServiceInitializationInformation) (int64, error) {
|
|
return getQuotaOrUsed(userID, information, 4)
|
|
}
|
|
|
|
func deleteNote(userID uuid.UUID, noteID uuid.UUID, information library.ServiceInitializationInformation) error {
|
|
response, err := askBlobService(nucleusLibrary.File{
|
|
User: userID,
|
|
Name: noteID.String(),
|
|
}, information, 2)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if response.MessageType != 0 {
|
|
return response.Message.(error)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func modifyNote(userID uuid.UUID, noteID uuid.UUID, data []byte, information library.ServiceInitializationInformation) error {
|
|
response, err := askBlobService(nucleusLibrary.File{
|
|
User: userID,
|
|
Name: noteID.String(),
|
|
Bytes: data,
|
|
}, information, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if response.MessageType != 0 {
|
|
return response.Message.(error)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func getNote(userID uuid.UUID, noteID uuid.UUID, information library.ServiceInitializationInformation) (*os.File, error) {
|
|
response, err := askBlobService(nucleusLibrary.File{
|
|
User: userID,
|
|
Name: noteID.String(),
|
|
}, information, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if response.MessageType != 0 {
|
|
return nil, response.Message.(error)
|
|
} else {
|
|
return response.Message.(*os.File), nil
|
|
}
|
|
}
|
|
|
|
func renderProtobuf(statusCode int, w http.ResponseWriter, protobuf proto.Message, information library.ServiceInitializationInformation) {
|
|
w.WriteHeader(statusCode)
|
|
data, err := proto.Marshal(protobuf)
|
|
if err != nil {
|
|
logFunc(err.Error(), 2, information)
|
|
}
|
|
w.Header().Add("Content-Type", "application/x-protobuf")
|
|
_, err = w.Write(data)
|
|
if err != nil {
|
|
logFunc(err.Error(), 2, information)
|
|
}
|
|
}
|
|
|
|
func verifyJWT(token string, publicKey ed25519.PublicKey) (jwt.MapClaims, error) {
|
|
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
|
return publicKey, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !parsedToken.Valid {
|
|
return nil, errors.New("invalid token")
|
|
}
|
|
|
|
claims, ok := parsedToken.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
return nil, errors.New("invalid token")
|
|
}
|
|
|
|
// Check if the token expired
|
|
date, err := claims.GetExpirationTime()
|
|
if err != nil || date.Before(time.Now()) || claims["sub"] == nil || claims["isOpenID"] == nil || claims["isOpenID"].(bool) {
|
|
return claims, errors.New("invalid token")
|
|
}
|
|
|
|
return claims, nil
|
|
}
|
|
|
|
func getUsername(token string, oauthHostName string, publicKey ed25519.PublicKey) (string, string, error) {
|
|
// Verify the JWT
|
|
_, err := verifyJWT(token, publicKey)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
// Get the user's information
|
|
var responseData struct {
|
|
Username string `json:"username"`
|
|
Sub string `json:"sub"`
|
|
}
|
|
request, err := http.NewRequest("GET", oauthHostName+"/api/oauth/userinfo", nil)
|
|
request.Header.Set("Authorization", "Bearer "+token)
|
|
response, err := http.DefaultClient.Do(request)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
if response.StatusCode != 200 || response.Body == nil || response.Body == http.NoBody {
|
|
return "", "", errors.New("invalid response")
|
|
}
|
|
|
|
err = json.NewDecoder(response.Body).Decode(&responseData)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return responseData.Sub, responseData.Username, nil
|
|
}
|
|
|
|
func Main(information library.ServiceInitializationInformation) {
|
|
var conn library.Database
|
|
hostName := information.Configuration["hostName"].(string)
|
|
|
|
// Initiate a connection to the database
|
|
// Call service ID 1 to get the database connection information
|
|
information.Outbox <- library.InterServiceMessage{
|
|
ServiceID: ServiceInformation.ServiceID,
|
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000001"), // Service initialization service
|
|
MessageType: 1, // Request connection information
|
|
SentAt: time.Now(),
|
|
Message: nil,
|
|
}
|
|
|
|
// Wait for the response
|
|
response := <-information.Inbox
|
|
if response.MessageType == 2 {
|
|
// This is the connection information
|
|
// Set up the database connection
|
|
conn = response.Message.(library.Database)
|
|
if conn.DBType == library.Sqlite {
|
|
// Create the users table
|
|
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB NOT NULL UNIQUE, publicKey BLOB NOT NULL, USERNAME TEXT NOT NULL)")
|
|
if err != nil {
|
|
logFunc(err.Error(), 3, information)
|
|
}
|
|
// Create the notes table
|
|
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS notes (id BLOB NOT NULL UNIQUE, userID BLOB NOT NULL, title BLOB NOT NULL, titleIV BLOB NOT NULL)")
|
|
if err != nil {
|
|
logFunc(err.Error(), 3, information)
|
|
}
|
|
} else {
|
|
// Create the users table
|
|
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA NOT NULL UNIQUE, publicKey BYTEA NOT NULL, USERNAME TEXT NOT NULL)")
|
|
if err != nil {
|
|
logFunc(err.Error(), 3, information)
|
|
}
|
|
// Create the notes table
|
|
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS notes (id BYTEA NOT NULL UNIQUE, userID BYTEA NOT NULL, title BYTEA NOT NULL, titleIV BYTEA NOT NULL)")
|
|
if err != nil {
|
|
logFunc(err.Error(), 3, information)
|
|
}
|
|
}
|
|
} else {
|
|
// This is an error message
|
|
// Log the error message to the logger service
|
|
logFunc(response.Message.(error).Error(), 3, information)
|
|
}
|
|
|
|
// Ask the authentication service for the public key
|
|
information.Outbox <- library.InterServiceMessage{
|
|
ServiceID: ServiceInformation.ServiceID,
|
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"), // Authentication service
|
|
MessageType: 2, // Request public key
|
|
SentAt: time.Now(),
|
|
Message: nil,
|
|
}
|
|
|
|
var publicKey ed25519.PublicKey = nil
|
|
|
|
// 3 second timeout
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
if publicKey == nil {
|
|
logFunc("Timeout while waiting for the public key from the authentication service", 3, information)
|
|
}
|
|
}()
|
|
|
|
// Wait for the response
|
|
response = <-information.Inbox
|
|
if response.MessageType == 2 {
|
|
// This is the public key
|
|
publicKey = response.Message.(ed25519.PublicKey)
|
|
} else {
|
|
// This is an error message
|
|
// Log the error message to the logger service
|
|
logFunc(response.Message.(error).Error(), 3, information)
|
|
}
|
|
|
|
// Ask the authentication service for the OAuth host name
|
|
information.Outbox <- library.InterServiceMessage{
|
|
ServiceID: ServiceInformation.ServiceID,
|
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"), // Authentication service
|
|
MessageType: 0, // Request OAuth host name
|
|
SentAt: time.Now(),
|
|
Message: nil,
|
|
}
|
|
|
|
var oauthHostName string
|
|
|
|
// 3 second timeout
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
if oauthHostName == "" {
|
|
logFunc("Timeout while waiting for the OAuth host name from the authentication service", 3, information)
|
|
}
|
|
}()
|
|
|
|
// Wait for the response
|
|
response = <-information.Inbox
|
|
if response.MessageType == 0 {
|
|
// This is the OAuth host name
|
|
oauthHostName = response.Message.(string)
|
|
} else {
|
|
// This is an error message
|
|
// Log the error message to the logger service
|
|
logFunc(response.Message.(error).Error(), 3, information)
|
|
}
|
|
|
|
// Ask the authentication service to create a new OAuth2 client
|
|
urlPath, err := url.JoinPath(hostName, "/oauth")
|
|
if err != nil {
|
|
logFunc(err.Error(), 3, information)
|
|
}
|
|
|
|
information.Outbox <- library.InterServiceMessage{
|
|
ServiceID: ServiceInformation.ServiceID,
|
|
ForServiceID: uuid.MustParse("00000000-0000-0000-0000-000000000004"), // Authentication service
|
|
MessageType: 1, // Create OAuth2 client
|
|
SentAt: time.Now(),
|
|
Message: nucleusLibrary.OAuthInformation{
|
|
Name: "Data Tracker",
|
|
RedirectUri: urlPath,
|
|
KeyShareUri: "",
|
|
Scopes: []string{"openid"},
|
|
},
|
|
}
|
|
|
|
oauthResponse := nucleusLibrary.OAuthResponse{}
|
|
|
|
// 3 second timeout
|
|
go func() {
|
|
time.Sleep(3 * time.Second)
|
|
if oauthResponse == (nucleusLibrary.OAuthResponse{}) {
|
|
logFunc("Timeout while waiting for the OAuth response from the authentication service", 3, information)
|
|
}
|
|
}()
|
|
|
|
// Wait for the response
|
|
response = <-information.Inbox
|
|
switch response.MessageType {
|
|
case 0:
|
|
// Success, set the OAuth response
|
|
oauthResponse = response.Message.(nucleusLibrary.OAuthResponse)
|
|
logFunc("Initialized with App ID: "+oauthResponse.AppID, 0, information)
|
|
case 1:
|
|
// An error which is their fault
|
|
logFunc(response.Message.(error).Error(), 3, information)
|
|
case 2:
|
|
// An error which is our fault
|
|
logFunc(response.Message.(error).Error(), 3, information)
|
|
default:
|
|
// An unknown error
|
|
logFunc("Unknown error", 3, information)
|
|
}
|
|
|
|
// Set up the router
|
|
router := information.Router
|
|
|
|
// Set up the static routes
|
|
staticDir, err := fs.Sub(information.ResourceDir, "static")
|
|
if err != nil {
|
|
logFunc(err.Error(), 3, information)
|
|
} else {
|
|
router.Handle("/bgn-static/*", http.StripPrefix("/bgn-static/", http.FileServerFS(staticDir)))
|
|
}
|
|
|
|
// Set up the routes
|
|
router.Post("/api/notes/add", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.Token
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
|
|
// Verify the JWT
|
|
claims, err := verifyJWT(requestData.Token, publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Generate a new note UUID
|
|
noteID := uuid.New()
|
|
|
|
// Check if the user has reached their quota
|
|
quota, err := getQuota(uuid.MustParse(claims["sub"].(string)), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x02}}, information)
|
|
return
|
|
}
|
|
|
|
used, err := getUsed(uuid.MustParse(claims["sub"].(string)), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x03}}, information)
|
|
return
|
|
}
|
|
|
|
if used >= quota {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Quota reached"}, information)
|
|
return
|
|
}
|
|
|
|
// Try to insert the note into the database
|
|
_, err = conn.DB.Exec("INSERT INTO notes (id, userID) VALUES ($1, $2)", noteID, claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x04}}, information)
|
|
} else {
|
|
noteIdBytes, err := noteID.MarshalBinary()
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x05}}, information)
|
|
}
|
|
renderProtobuf(200, w, &protobuf.NoteID{NoteId: noteIdBytes}, information)
|
|
}
|
|
})
|
|
|
|
router.Post("/api/notes/remove", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.NoteRequest
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
// Verify the JWT
|
|
claims, err := verifyJWT(requestData.Token.String(), publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Try to remove the note from the database
|
|
_, err = conn.DB.Exec("DELETE FROM notes WHERE id = $1 AND userID = $2", requestData.NoteId, claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x06}}, information)
|
|
}
|
|
|
|
// If it's there, try to remove the note from the blob storage
|
|
err = deleteNote(uuid.MustParse(claims["sub"].(string)), uuid.Must(uuid.FromBytes(requestData.NoteId.GetNoteId())), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x07}}, information)
|
|
}
|
|
|
|
w.WriteHeader(200)
|
|
})
|
|
|
|
router.Post("/api/notes/list", func(w http.ResponseWriter, r *http.Request) {
|
|
// Verify the JWT
|
|
var requestData protobuf.Token
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
claims, err := verifyJWT(requestData.Token, publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Try to get the notes from the database
|
|
rows, err := conn.DB.Query("SELECT id, title, titleIV FROM notes WHERE userID = $1", claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x08}}, information)
|
|
return
|
|
}
|
|
|
|
// Create the notes list
|
|
var notes protobuf.ApiNotesListResponse
|
|
|
|
// Iterate through the rows
|
|
for rows.Next() {
|
|
var title, titleIV, noteID []byte
|
|
err = rows.Scan(¬eID, &title, &titleIV)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x09}}, information)
|
|
return
|
|
}
|
|
|
|
// Append the note to the list
|
|
notes.Notes = append(notes.Notes, &protobuf.NoteMetadata{
|
|
NoteId: &protobuf.NoteID{
|
|
NoteId: noteID,
|
|
},
|
|
Title: &protobuf.AESData{
|
|
Data: title,
|
|
Iv: titleIV,
|
|
},
|
|
})
|
|
}
|
|
|
|
// Render the notes list
|
|
renderProtobuf(200, w, ¬es, information)
|
|
})
|
|
|
|
router.Post("/api/notes/get", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.NoteRequest
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
// Verify the JWT
|
|
claims, err := verifyJWT(requestData.Token.String(), publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Try to get the note from the database
|
|
var title, titleIV []byte
|
|
err = conn.DB.QueryRow("SELECT title, titleIV FROM notes WHERE id = $1 AND userID = $2", requestData.NoteId, claims["sub"]).Scan(&title, &titleIV)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x0A}}, information)
|
|
return
|
|
}
|
|
|
|
// Get the note from the blob storage
|
|
noteFile, err := getNote(uuid.MustParse(claims["sub"].(string)), uuid.Must(uuid.FromBytes(requestData.NoteId.GetNoteId())), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x0B}}, information)
|
|
return
|
|
}
|
|
|
|
// The IV is the first 16 bytes of the file
|
|
iv := make([]byte, 16)
|
|
_, err = noteFile.Read(iv)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x0C}}, information)
|
|
return
|
|
}
|
|
|
|
// The rest of the file is the data
|
|
data, err := io.ReadAll(noteFile)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x0D}}, information)
|
|
return
|
|
}
|
|
|
|
// Close the file
|
|
err = noteFile.Close()
|
|
if err != nil {
|
|
logFunc("Resource leak in /api/notes/get", 2, information)
|
|
}
|
|
|
|
// Render the note
|
|
renderProtobuf(200, w, &protobuf.Note{
|
|
Note: &protobuf.AESData{
|
|
Data: data,
|
|
Iv: iv,
|
|
},
|
|
Metadata: &protobuf.NoteMetadata{
|
|
NoteId: requestData.NoteId,
|
|
Title: &protobuf.AESData{
|
|
Data: title,
|
|
Iv: titleIV,
|
|
},
|
|
},
|
|
}, information)
|
|
})
|
|
|
|
router.Post("/api/notes/edit", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.ApiNotesEditRequest
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
// Verify the JWT
|
|
claims, err := verifyJWT(requestData.Token.String(), publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Update the title
|
|
_, err = conn.DB.Exec("UPDATE notes SET title = $1, titleIV = $2 WHERE id = $3 AND userID = $4", requestData.Note.Metadata.Title.Data, requestData.Note.Metadata.Title.Iv, requestData.Note.Metadata.NoteId, claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x0E}}, information)
|
|
return
|
|
}
|
|
|
|
// Edit the note in the blob storage
|
|
err = modifyNote(uuid.MustParse(claims["sub"].(string)), uuid.Must(uuid.FromBytes(requestData.Note.Metadata.NoteId.GetNoteId())), bytes.Join([][]byte{requestData.Note.Note.Iv, requestData.Note.Note.Data}, nil), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x0F}}, information)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(200)
|
|
})
|
|
|
|
router.Post("/api/notes/purge", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.Token
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
// Verify the JWT
|
|
claims, err := verifyJWT(requestData.Token, publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Get the notes from the database
|
|
rows, err := conn.DB.Query("SELECT id FROM notes WHERE userID = $1", claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x10}}, information)
|
|
return
|
|
}
|
|
|
|
// Iterate through the rows
|
|
for rows.Next() {
|
|
var noteID []byte
|
|
err = rows.Scan(¬eID)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x11}}, information)
|
|
return
|
|
}
|
|
|
|
// Try to remove the note from the blob storage
|
|
err = deleteNote(uuid.MustParse(claims["sub"].(string)), uuid.Must(uuid.FromBytes(noteID)), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x12}}, information)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Remove the notes from the database
|
|
_, err = conn.DB.Exec("DELETE FROM notes WHERE userID = $1", claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x13}}, information)
|
|
return
|
|
}
|
|
})
|
|
|
|
router.Post("/api/signup", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.ApiSignupRequest
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
// Verify the JWT
|
|
sub, username, err := getUsername(requestData.Token.String(), oauthHostName, publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Try to insert the user into the database
|
|
_, err = conn.DB.Exec("INSERT INTO users (id, publicKey, username) VALUES ($1, $2, $3)", sub, requestData.PublicKey, username)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "UNIQUE") {
|
|
renderProtobuf(409, w, &protobuf.Error{Error: "User already exists"}, information)
|
|
} else {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x01}}, information)
|
|
}
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(200)
|
|
})
|
|
|
|
router.Post("/api/delete", func(w http.ResponseWriter, r *http.Request) {
|
|
var requestData protobuf.Token
|
|
err := unmarshalProtobuf(r, &requestData)
|
|
if err != nil {
|
|
renderProtobuf(400, w, &protobuf.Error{Error: "Invalid request"}, information)
|
|
return
|
|
}
|
|
|
|
// Verify the JWT
|
|
claims, err := verifyJWT(requestData.Token, publicKey)
|
|
if err != nil {
|
|
renderProtobuf(403, w, &protobuf.Error{Error: "Invalid token"}, information)
|
|
return
|
|
}
|
|
|
|
// Try to remove the user from the database
|
|
_, err = conn.DB.Exec("DELETE FROM users WHERE id = $1", claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x14}}, information)
|
|
return
|
|
}
|
|
|
|
// Get the notes from the database
|
|
rows, err := conn.DB.Query("SELECT id FROM notes WHERE userID = $1", claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x15}}, information)
|
|
return
|
|
}
|
|
|
|
// Iterate through the rows
|
|
for rows.Next() {
|
|
var noteID []byte
|
|
err = rows.Scan(¬eID)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x16}}, information)
|
|
return
|
|
}
|
|
|
|
// Try to remove the note from the blob storage
|
|
err = deleteNote(uuid.MustParse(claims["sub"].(string)), uuid.Must(uuid.FromBytes(noteID)), information)
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x17}}, information)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Remove the notes from the database
|
|
_, err = conn.DB.Exec("DELETE FROM notes WHERE userID = $1", claims["sub"])
|
|
if err != nil {
|
|
renderProtobuf(500, w, &protobuf.ServerError{ErrorCode: []byte{0x18}}, information)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(200)
|
|
})
|
|
|
|
// TODO: Implement shared notes
|
|
}
|