Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

6 changed files with 186 additions and 87 deletions

View file

@ -5,19 +5,8 @@ resourceDir="$path/../../resources/322dc186-04d2-4f69-89b5-403ab643cc1d"
rm -rf "$resourceDir" || exit 1
rm -rf "$path/../../services/data-tracker.fgs" || exit 1
cd "$path" || exit 1
printf "\033[1;35mBuilding data-tracker.fgs...\033[0m\n"
go build -o "$path/../../services/data-tracker.fgs" --buildmode=plugin -ldflags "-s -w" || exit 1
cd "$path/resources/wasm/oauth" || exit 1
find -L "$path/resources/wasm" -type f -name "main.go" | while read -r mainGo; do
buildDir=$(dirname "$mainGo")
baseName=$(basename "$buildDir")
printf "\033[1;34m\033[1;33mBuilding WASM object %s...\033[0m\n" "$baseName"
(cd "$buildDir" && GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/$(basename "$buildDir").wasm" -ldflags "-s -w") || {
printf "\033[1;31mError: %s failed.\033[0m\n" "$mainGo"
exit 1
}
done
printf "\033[1;34mCopying static files...\033[0m\n"
GOOS=js GOARCH=wasm go build -o "$resourceDir/static/wasm/oauth.wasm" -ldflags "-s -w" || exit 1
cp -r "$path/resources/static" "$resourceDir/" || exit 1
cp -r "$path/resources/templates" "$resourceDir/" || exit 1
printf "\033[1;36mdata-tracker.fgs has been built successfully!\033[0m\n"

8
go.mod
View file

@ -1,12 +1,12 @@
module git.ailur.dev/ailur/datatracker
go 1.23.3
go 1.23.1
require (
git.ailur.dev/ailur/fg-library/v3 v3.6.2
git.ailur.dev/ailur/fg-nucleus-library v1.2.2
git.ailur.dev/ailur/fg-library/v2 v2.0.1
git.ailur.dev/ailur/fg-nucleus-library v1.0.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
)
require github.com/go-chi/chi/v5 v5.2.1 // indirect
require github.com/go-chi/chi/v5 v5.1.0

14
go.sum
View file

@ -1,9 +1,11 @@
git.ailur.dev/ailur/fg-library/v3 v3.6.2 h1:PNJKxpvbel2iDeB9+/rpYRyMoim6JjRHOXPYFYky7Ng=
git.ailur.dev/ailur/fg-library/v3 v3.6.2/go.mod h1:ArNsafpqES2JuxQM5aM+bNe0FwHLIsL6pbjpiWvDwGs=
git.ailur.dev/ailur/fg-nucleus-library v1.2.2 h1:JbclmxGSoL+ByGZAl0W6PqWRoyBBGTrKrizWDJ7rdI0=
git.ailur.dev/ailur/fg-nucleus-library v1.2.2/go.mod h1:stxiTyMv3Fa7GzpyLbBUh3ahlb7110p0NnCl8ZTjwBs=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
git.ailur.dev/ailur/fg-library/v2 v2.0.0 h1:NanDV52W+NBu96v/HPDPGqH8NOxLp6MRrRdXLPEsgYw=
git.ailur.dev/ailur/fg-library/v2 v2.0.0/go.mod h1:1jYbWhabGcIwp7CkhHqvRwC8eP+nHv5BrXPe9NX2HE8=
git.ailur.dev/ailur/fg-library/v2 v2.0.1 h1:ltPYXf/Om0hnMD8gr1K5bkYrfHqKPSbb0hxa0wtTnZ0=
git.ailur.dev/ailur/fg-library/v2 v2.0.1/go.mod h1:1jYbWhabGcIwp7CkhHqvRwC8eP+nHv5BrXPe9NX2HE8=
git.ailur.dev/ailur/fg-nucleus-library v1.0.0 h1:TT1V4cfka+uUpvV1zU7bc4KXFkgnsI/sIvaZDDxXk+k=
git.ailur.dev/ailur/fg-nucleus-library v1.0.0/go.mod h1:m4gNSEypfgrUV8bXaR8NLB8zchUM59y0ellV1wp/C+I=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

229
main.go
View file

@ -2,14 +2,16 @@ package main
import (
"errors"
"net/url"
"time"
"crypto/ed25519"
"database/sql"
"encoding/json"
library "git.ailur.dev/ailur/fg-library/v3"
library "git.ailur.dev/ailur/fg-library/v2"
authLibrary "git.ailur.dev/ailur/fg-nucleus-library"
"github.com/go-chi/chi/v5"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"html/template"
@ -22,7 +24,6 @@ var ServiceInformation = library.Service{
Permissions: library.Permissions{
Authenticate: true, // This service does require authentication
Database: true, // This service does require database access
Router: true, // This service does require a router
BlobStorage: false, // This service does not require blob storage
InterServiceCommunication: true, // This service does require inter-service communication
Resources: true, // This service does require its HTTP templates and static files
@ -30,16 +31,18 @@ var ServiceInformation = library.Service{
ServiceID: uuid.MustParse("322dc186-04d2-4f69-89b5-403ab643cc1d"),
}
var (
loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002")
)
func logFunc(message string, messageType library.MessageCode, information *library.ServiceInitializationInformation) {
func logFunc(message string, messageType uint64, information library.ServiceInitializationInformation) {
// Log the message to the logger service
information.SendISMessage(loggerService, messageType, message)
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 renderTemplate(statusCode int, w http.ResponseWriter, data map[string]interface{}, templatePath string, information *library.ServiceInitializationInformation) {
func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]interface{}, templatePath string, information library.ServiceInitializationInformation) {
var err error
var requestedTemplate *template.Template
// Output ls of the resource directory
@ -57,7 +60,7 @@ func renderTemplate(statusCode int, w http.ResponseWriter, data map[string]inter
}
}
func renderString(statusCode int, w http.ResponseWriter, data string, information *library.ServiceInitializationInformation) {
func renderString(statusCode int, w http.ResponseWriter, data string, information library.ServiceInitializationInformation) {
w.WriteHeader(statusCode)
_, err := w.Write([]byte(data))
if err != nil {
@ -65,7 +68,7 @@ func renderString(statusCode int, w http.ResponseWriter, data string, informatio
}
}
func renderJSON(statusCode int, w http.ResponseWriter, data map[string]interface{}, information *library.ServiceInitializationInformation) {
func renderJSON(statusCode int, w http.ResponseWriter, data map[string]interface{}, information library.ServiceInitializationInformation) {
w.WriteHeader(statusCode)
err := json.NewEncoder(w).Encode(data)
if err != nil {
@ -102,10 +105,6 @@ func getUsername(token string, oauthHostName string, publicKey ed25519.PublicKey
Sub string `json:"sub"`
}
request, err := http.NewRequest("GET", oauthHostName+"/api/oauth/userinfo", nil)
if err != nil {
return "", "", err
}
request.Header.Set("Authorization", "Bearer "+token)
response, err := http.DefaultClient.Do(request)
if err != nil {
@ -162,66 +161,174 @@ func verifyJwt(token string, publicKey ed25519.PublicKey, conn library.Database)
return claims, true
}
func Main(information *library.ServiceInitializationInformation) {
func Main(information library.ServiceInitializationInformation) *chi.Mux {
var conn library.Database
hostName := information.Configuration["hostName"].(string)
go information.StartISProcessor()
// Initiate a connection to the database
conn, err := information.GetDatabase()
if err != nil {
logFunc(err.Error(), 3, information)
return
// 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,
}
if conn.DBType == library.Sqlite {
// Create the RFCs table
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS rfc (id INTEGER NOT NULL, year INTEGER NOT NULL, name TEXT NOT NULL, content TEXT NOT NULL, version TEXT NOT NULL, creator BLOB NOT NULL, UNIQUE(id, year))")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the comments table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS comments (id BLOB NOT NULL, rfcId INTEGER NOT NULL, rfcYear INTEGER NOT NULL, content TEXT NOT NULL, creator BLOB NOT NULL, creatorName TEXT NOT NULL, created INTEGER NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
// 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 RFCs table
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS rfc (id INTEGER NOT NULL, year INTEGER NOT NULL, name TEXT NOT NULL, content TEXT NOT NULL, version TEXT NOT NULL, creator BLOB NOT NULL, UNIQUE(id, year))")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BLOB NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the comments table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS comments (id BLOB NOT NULL, rfcId INTEGER NOT NULL, rfcYear INTEGER NOT NULL, content TEXT NOT NULL, creator BLOB NOT NULL, creatorName TEXT NOT NULL, created INTEGER NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
} else {
// Create the RFCs table
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS rfc (id SERIAL PRIMARY KEY, year INTEGER NOT NULL, name TEXT NOT NULL, content TEXT NOT NULL, version TEXT NOT NULL, creator BYTEA NOT NULL, UNIQUE(id, year))")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the comments table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS comments (id BYTEA NOT NULL, rfcId INTEGER NOT NULL, rfcYear INTEGER NOT NULL, content TEXT NOT NULL, creator BYTEA NOT NULL, creatorName TEXT NOT NULL, created INTEGER NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
}
} else {
// Create the RFCs table
_, err := conn.DB.Exec("CREATE TABLE IF NOT EXISTS rfc (id SERIAL PRIMARY KEY, year INTEGER NOT NULL, name TEXT NOT NULL, content TEXT NOT NULL, version TEXT NOT NULL, creator BYTEA NOT NULL, UNIQUE(id, year))")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the users table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS users (id BYTEA NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// Create the comments table
_, err = conn.DB.Exec("CREATE TABLE IF NOT EXISTS comments (id BYTEA NOT NULL, rfcId INTEGER NOT NULL, rfcYear INTEGER NOT NULL, content TEXT NOT NULL, creator BYTEA NOT NULL, creatorName TEXT NOT NULL, created INTEGER NOT NULL)")
if err != nil {
logFunc(err.Error(), 3, information)
}
// This is an error message
// Log the error message to the logger service
logFunc(response.Message.(error).Error(), 3, information)
}
// Initialize the OAuth
oauthResponse, publicKey, oauthHostName, err := authLibrary.InitializeOAuth(authLibrary.OAuthInformation{
Name: "datatracker",
RedirectUri: hostName + "/oauth",
Scopes: []string{"openid"},
}, 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)
return
}
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: authLibrary.OAuthInformation{
Name: "Data Tracker",
RedirectUri: urlPath,
KeyShareUri: "",
Scopes: []string{"openid"},
},
}
oauthResponse := authLibrary.OAuthResponse{}
// 3 second timeout
go func() {
time.Sleep(3 * time.Second)
if oauthResponse == (authLibrary.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.(authLibrary.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
router := chi.NewRouter()
// Set up the static routes
staticDir, err := fs.Sub(information.ResourceDir, "static")
@ -535,4 +642,6 @@ func Main(information *library.ServiceInitializationInformation) {
"AuthorizationUri": oauthHostName,
}, "oauth.html", information)
})
return router
}

View file

@ -59,8 +59,8 @@
"Content-Type": "application/json"
},
body: JSON.stringify({
year: parseInt(year),
id: parseInt(id),
year: year,
id: id,
token: localStorage.getItem("SECRET-token")
})
}).then(function(response) {

View file

@ -41,7 +41,9 @@ func randomChars(length int) (string, error) {
}
func main() {
// Clear local storage
localStorage := js.Global().Get("localStorage")
localStorage.Call("clear")
statusBox := js.Global().Get("document").Call("getElementById", "statusBox")
tryAgain := js.Global().Get("document").Call("getElementById", "tryAgain")
@ -173,9 +175,6 @@ func main() {
tryAgain.Set("style", "")
}
} else {
// Clear local storage
localStorage.Call("clear")
// Start the authorization process
verifier, err := randomChars(128)
if err != nil {