2024-06-21 01:46:21 +01:00
package main
import (
2024-10-20 10:45:03 +01:00
"bytes"
"git.ailur.dev/ailur/burgernotes/git.ailur.dev/ailur/burgernotes/protobuf"
2024-06-21 01:46:21 +01:00
"errors"
2024-10-20 10:45:03 +01:00
"io"
2024-06-21 01:46:21 +01:00
"os"
"strings"
"time"
2024-10-20 10:45:03 +01:00
"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/go-chi/chi/v5"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"google.golang.org/protobuf/proto"
2024-06-21 01:46:21 +01:00
)
2024-10-20 10:45:03 +01:00
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" ) ,
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
func unmarshalProtobuf ( r * http . Request , protobuf proto . Message ) error {
var protobufData [ ] byte
_ , err := r . Body . Read ( protobufData )
if err != nil {
return err
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
err = proto . Unmarshal ( protobufData , protobuf )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
return err
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
return nil
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
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 ,
2024-06-21 01:46:21 +01:00
}
}
2024-10-20 10:45:03 +01:00
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 ,
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
// 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 )
} ( )
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Wait for the response
select {
case response := <- information . Inbox :
return response , nil
case <- timeoutChan :
return library . InterServiceMessage { } , errors . New ( "timeout" )
}
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
func getQuotaOrUsed ( userID uuid . UUID , information library . ServiceInitializationInformation , context uint64 ) ( int64 , error ) {
response , err := askBlobService ( userID , information , context )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
return 0 , err
} else if response . MessageType != 0 {
return 0 , response . Message . ( error )
} else {
return response . Message . ( int64 ) , nil
2024-06-21 01:46:21 +01:00
}
}
2024-10-20 10:45:03 +01:00
func getQuota ( userID uuid . UUID , information library . ServiceInitializationInformation ) ( int64 , error ) {
return getQuotaOrUsed ( userID , information , 3 )
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
func getUsed ( userID uuid . UUID , information library . ServiceInitializationInformation ) ( int64 , error ) {
return getQuotaOrUsed ( userID , information , 4 )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
func deleteNote ( userID uuid . UUID , noteID uuid . UUID , information library . ServiceInitializationInformation ) error {
response , err := askBlobService ( nucleusLibrary . File {
User : userID ,
Name : noteID . String ( ) ,
} , information , 2 )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
return err
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
if response . MessageType != 0 {
return response . Message . ( error )
} else {
return nil
2024-06-21 01:46:21 +01:00
}
}
2024-10-20 10:45:03 +01:00
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 )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
return err
}
if response . MessageType != 0 {
return response . Message . ( error )
2024-06-21 01:46:21 +01:00
} else {
2024-10-20 10:45:03 +01:00
return nil
2024-06-21 01:46:21 +01:00
}
}
2024-10-20 10:45:03 +01:00
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 )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
return nil , err
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
if response . MessageType != 0 {
return nil , response . Message . ( error )
} else {
return response . Message . ( * os . File ) , nil
2024-06-21 01:46:21 +01:00
}
}
2024-10-20 10:45:03 +01:00
func renderProtobuf ( statusCode int , w http . ResponseWriter , protobuf proto . Message , information library . ServiceInitializationInformation ) {
w . WriteHeader ( statusCode )
data , err := proto . Marshal ( protobuf )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
logFunc ( err . Error ( ) , 2 , information )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
w . Header ( ) . Add ( "Content-Type" , "application/x-protobuf" )
_ , err = w . Write ( data )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
logFunc ( err . Error ( ) , 2 , information )
2024-06-21 01:46:21 +01:00
}
}
2024-10-20 10:45:03 +01:00
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
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
if ! parsedToken . Valid {
return nil , errors . New ( "invalid token" )
2024-06-24 16:55:48 +01:00
}
2024-10-20 10:45:03 +01:00
claims , ok := parsedToken . Claims . ( jwt . MapClaims )
if ! ok {
return nil , errors . New ( "invalid token" )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
// 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" )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
return claims , nil
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
func getUsername ( token string , oauthHostName string , publicKey ed25519 . PublicKey ) ( string , string , error ) {
// Verify the JWT
_ , err := verifyJWT ( token , publicKey )
if err != nil {
return "" , "" , err
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
// Get the user's information
var responseData struct {
Username string ` json:"username" `
Sub string ` json:"sub" `
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
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
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
if response . StatusCode != 200 || response . Body == nil || response . Body == http . NoBody {
return "" , "" , errors . New ( "invalid response" )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
err = json . NewDecoder ( response . Body ) . Decode ( & responseData )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
return "" , "" , err
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
return responseData . Sub , responseData . Username , nil
}
2024-07-20 16:45:21 +01:00
2024-10-20 10:45:03 +01:00
func Main ( information library . ServiceInitializationInformation ) * chi . Mux {
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 ,
2024-07-20 16:45:21 +01:00
}
2024-10-20 10:45:03 +01:00
// 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 )
}
2024-07-21 09:45:47 +01:00
} else {
2024-10-20 10:45:03 +01:00
// 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 )
}
2024-07-21 09:45:47 +01:00
}
2024-10-20 10:45:03 +01:00
} else {
// This is an error message
// Log the error message to the logger service
logFunc ( response . Message . ( error ) . Error ( ) , 3 , information )
2024-07-21 09:45:47 +01:00
}
2024-10-20 10:45:03 +01:00
// 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 ,
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
var publicKey ed25519 . PublicKey = nil
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
} ( )
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// 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 )
}
2024-06-27 18:29:20 +01:00
2024-10-20 10:45:03 +01:00
// 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 ,
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
var oauthHostName string
2024-07-20 16:45:21 +01:00
2024-10-20 10:45:03 +01:00
// 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 )
2024-07-21 09:45:47 +01:00
}
2024-10-20 10:45:03 +01:00
} ( )
2024-07-21 09:45:47 +01:00
2024-10-20 10:45:03 +01:00
// 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 )
}
2024-07-21 09:45:47 +01:00
2024-10-20 10:45:03 +01:00
// Ask the authentication service to create a new OAuth2 client
urlPath , err := url . JoinPath ( hostName , "/oauth" )
if err != nil {
logFunc ( err . Error ( ) , 3 , information )
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
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" } ,
} ,
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
oauthResponse := nucleusLibrary . OAuthResponse { }
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
} ( )
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// 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 )
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Set up the router
router := chi . NewRouter ( )
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// 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 ) ) )
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Set up the routes
router . Post ( "/api/notes/add" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . Token
err := unmarshalProtobuf ( r , & requestData )
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Verify the JWT
claims , err := verifyJWT ( requestData . Token , publicKey )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-07-21 09:09:30 +01:00
return
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Generate a new note UUID
noteID := uuid . New ( )
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Check if the user has reached their quota
quota , err := getQuota ( uuid . MustParse ( claims [ "sub" ] . ( string ) ) , information )
2024-07-20 20:35:54 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x02 } } , information )
2024-07-20 20:35:54 +01:00
return
}
2024-10-20 10:45:03 +01:00
used , err := getUsed ( uuid . MustParse ( claims [ "sub" ] . ( string ) ) , information )
2024-07-20 10:52:18 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x03 } } , information )
2024-07-20 10:52:18 +01:00
return
2024-06-21 01:46:21 +01:00
}
2024-06-26 18:53:58 +01:00
2024-10-20 10:45:03 +01:00
if used >= quota {
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Quota reached" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Try to insert the note into the database
_ , err = conn . DB . Exec ( "INSERT INTO notes (id, userID) VALUES ($1, $2)" , noteID , claims [ "sub" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x04 } } , information )
2024-07-21 09:45:47 +01:00
} else {
2024-10-20 10:45:03 +01:00
noteIdBytes , err := noteID . MarshalBinary ( )
if err != nil {
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x05 } } , information )
2024-06-24 16:55:48 +01:00
}
2024-10-20 10:45:03 +01:00
renderProtobuf ( 200 , w , & protobuf . NoteID { NoteId : noteIdBytes } , information )
2024-06-24 16:55:48 +01:00
}
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/notes/remove" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . NoteRequest
err := unmarshalProtobuf ( r , & requestData )
2024-06-24 16:55:48 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-07-20 10:52:18 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Verify the JWT
claims , err := verifyJWT ( requestData . Token . String ( ) , publicKey )
2024-07-20 10:52:18 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-26 18:53:58 +01:00
return
}
2024-07-20 10:52:18 +01:00
2024-10-20 10:45:03 +01:00
// 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" ] )
2024-06-24 16:55:48 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x06 } } , information )
2024-06-24 16:55:48 +01:00
}
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-24 16:55:48 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x07 } } , information )
2024-06-24 16:55:48 +01:00
}
2024-10-20 10:45:03 +01:00
w . WriteHeader ( 200 )
2024-06-24 16:55:48 +01:00
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/notes/list" , func ( w http . ResponseWriter , r * http . Request ) {
// Verify the JWT
var requestData protobuf . Token
err := unmarshalProtobuf ( r , & requestData )
2024-06-24 16:55:48 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-07-21 09:09:30 +01:00
return
}
2024-06-24 16:55:48 +01:00
2024-10-20 10:45:03 +01:00
claims , err := verifyJWT ( requestData . Token , publicKey )
2024-06-24 16:55:48 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-24 16:55:48 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Try to get the notes from the database
rows , err := conn . DB . Query ( "SELECT id, title, titleIV FROM notes WHERE userID = $1" , claims [ "sub" ] )
2024-06-24 16:55:48 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x08 } } , information )
2024-06-24 16:55:48 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Create the notes list
var notes protobuf . ApiNotesListResponse
2024-06-24 16:55:48 +01:00
2024-10-20 10:45:03 +01:00
// Iterate through the rows
for rows . Next ( ) {
var title , titleIV , noteID [ ] byte
err = rows . Scan ( & noteID , & title , & titleIV )
2024-07-21 09:09:30 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x09 } } , information )
2024-07-21 09:09:30 +01:00
return
}
2024-10-20 10:45:03 +01:00
// 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 ,
} ,
} )
2024-07-21 09:09:30 +01:00
}
2024-10-20 10:45:03 +01:00
// Render the notes list
renderProtobuf ( 200 , w , & notes , information )
2024-06-21 01:46:21 +01:00
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/notes/get" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . NoteRequest
err := unmarshalProtobuf ( r , & requestData )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Verify the JWT
claims , err := verifyJWT ( requestData . Token . String ( ) , publicKey )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x0A } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Get the note from the blob storage
noteFile , err := getNote ( uuid . MustParse ( claims [ "sub" ] . ( string ) ) , uuid . Must ( uuid . FromBytes ( requestData . NoteId . GetNoteId ( ) ) ) , information )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x0B } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// The IV is the first 16 bytes of the file
iv := make ( [ ] byte , 16 )
_ , err = noteFile . Read ( iv )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x0C } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// The rest of the file is the data
data , err := io . ReadAll ( noteFile )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x0D } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Close the file
err = noteFile . Close ( )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
logFunc ( "Resource leak in /api/notes/get" , 2 , information )
2024-06-21 01:46:21 +01:00
}
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/notes/edit" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . ApiNotesEditRequest
err := unmarshalProtobuf ( r , & requestData )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Verify the JWT
claims , err := verifyJWT ( requestData . Token . String ( ) , publicKey )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// 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" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x0E } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x0F } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
w . WriteHeader ( 200 )
2024-06-21 01:46:21 +01:00
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/notes/purge" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . Token
err := unmarshalProtobuf ( r , & requestData )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-06-26 18:53:58 +01:00
return
}
2024-06-21 01:46:21 +01:00
2024-10-20 10:45:03 +01:00
// Verify the JWT
claims , err := verifyJWT ( requestData . Token , publicKey )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Get the notes from the database
rows , err := conn . DB . Query ( "SELECT id FROM notes WHERE userID = $1" , claims [ "sub" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x10 } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Iterate through the rows
for rows . Next ( ) {
var noteID [ ] byte
err = rows . Scan ( & noteID )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x11 } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Try to remove the note from the blob storage
err = deleteNote ( uuid . MustParse ( claims [ "sub" ] . ( string ) ) , uuid . Must ( uuid . FromBytes ( noteID ) ) , information )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x12 } } , information )
2024-06-21 01:46:21 +01:00
return
}
}
2024-10-20 10:45:03 +01:00
// Remove the notes from the database
_ , err = conn . DB . Exec ( "DELETE FROM notes WHERE userID = $1" , claims [ "sub" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x13 } } , information )
2024-06-21 01:46:21 +01:00
return
}
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/signup" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . ApiSignupRequest
err := unmarshalProtobuf ( r , & requestData )
2024-06-27 17:46:50 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-06-27 17:46:50 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Verify the JWT
sub , username , err := getUsername ( requestData . Token . String ( ) , oauthHostName , publicKey )
2024-06-27 17:46:50 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-27 17:46:50 +01:00
return
}
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
if strings . Contains ( err . Error ( ) , "UNIQUE" ) {
renderProtobuf ( 409 , w , & protobuf . Error { Error : "User already exists" } , information )
2024-06-21 01:46:21 +01:00
} else {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x01 } } , information )
2024-06-21 01:46:21 +01:00
}
return
}
2024-10-20 10:45:03 +01:00
w . WriteHeader ( 200 )
2024-06-21 01:46:21 +01:00
} )
2024-10-20 10:45:03 +01:00
router . Post ( "/api/delete" , func ( w http . ResponseWriter , r * http . Request ) {
var requestData protobuf . Token
err := unmarshalProtobuf ( r , & requestData )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 400 , w , & protobuf . Error { Error : "Invalid request" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Verify the JWT
claims , err := verifyJWT ( requestData . Token , publicKey )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 403 , w , & protobuf . Error { Error : "Invalid token" } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Try to remove the user from the database
_ , err = conn . DB . Exec ( "DELETE FROM users WHERE id = $1" , claims [ "sub" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x14 } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Get the notes from the database
rows , err := conn . DB . Query ( "SELECT id FROM notes WHERE userID = $1" , claims [ "sub" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x15 } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// Iterate through the rows
2024-06-21 01:46:21 +01:00
for rows . Next ( ) {
2024-10-20 10:45:03 +01:00
var noteID [ ] byte
err = rows . Scan ( & noteID )
if err != nil {
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x16 } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
// 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 )
2024-06-21 01:46:21 +01:00
return
}
}
2024-10-20 10:45:03 +01:00
// Remove the notes from the database
_ , err = conn . DB . Exec ( "DELETE FROM notes WHERE userID = $1" , claims [ "sub" ] )
2024-06-21 01:46:21 +01:00
if err != nil {
2024-10-20 10:45:03 +01:00
renderProtobuf ( 500 , w , & protobuf . ServerError { ErrorCode : [ ] byte { 0x18 } } , information )
2024-06-21 01:46:21 +01:00
return
}
2024-10-20 10:45:03 +01:00
w . WriteHeader ( 200 )
2024-06-21 01:46:21 +01:00
} )
2024-10-20 10:45:03 +01:00
// TODO: Implement shared notes
2024-07-21 09:45:47 +01:00
2024-10-20 10:45:03 +01:00
return router
2024-06-21 01:46:21 +01:00
}