Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ target/

# Exclude sql files
*.sql

config.docker.yaml
10 changes: 6 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
FROM golang:1.23-alpine

WORKDIR /usr/app
WORKDIR /app

COPY . /usr/app/
COPY . /app/

RUN export GOPROXY=direct

RUN go build -o nymeria ./cmd/nymeria/main.go

EXPOSE 9898

# install make, psql
RUN apk add --no-cache make postgresql-client
RUN make build

CMD ["./nymeria"]
13 changes: 12 additions & 1 deletion api/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ func HandleCreateApplication(c *gin.Context) {
return
}

err = db.CreateApplication(body.Name, body.RedirectURL, body.AllowedDomains, body.Organization, helper.RandomString(10), helper.RandomString(30))
clientKey := helper.RandomString(10)
clientSecret := helper.RandomString(30)

err = db.CreateApplication(body.Name, body.RedirectURL, body.AllowedDomains, body.Organization, clientKey, clientSecret)

if err != nil {
log.ErrorLogger("Create application failed", err)
Expand All @@ -62,6 +65,14 @@ func HandleCreateApplication(c *gin.Context) {

c.JSON(http.StatusOK, gin.H{
"message": "application created",
"application_credentials": gin.H{
"name": body.Name,
"client_key": clientKey,
"client_secret": clientSecret,
"redirect_url": body.RedirectURL,
"allowed_domains": body.AllowedDomains,
"organization": body.Organization,
},
})

}
Expand Down
7 changes: 6 additions & 1 deletion api/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"net/http"
"time"

"github.com/gin-contrib/cors"
Expand Down Expand Up @@ -60,7 +61,11 @@ func Start() {
r.GET("/verify-session", HandleVerifySession)

// Application Authorization
r.POST("/verify-app", HandleAppAuthorization)
r.POST("/verify-app", middleware.HandleAppAuthorization, func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Authorized",
})
})

// Admin Routes
r.Use(middleware.OnlyAdmin)
Expand Down
72 changes: 0 additions & 72 deletions api/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ package api
import (
"context"
"net/http"
"strconv"
"strings"

"github.com/gin-gonic/gin"
client "github.com/ory/client-go"

"github.com/sdslabs/nymeria/config"
"github.com/sdslabs/nymeria/helper"
"github.com/sdslabs/nymeria/log"
"github.com/sdslabs/nymeria/pkg/db"
)

// HandleVerifySession handles the user session verification request
Expand Down Expand Up @@ -52,72 +49,3 @@ func HandleVerifySession(c *gin.Context) {
"message": "Session verified",
})
}

// HandleAppAuthorization handles the application authorization request
func HandleAppAuthorization(c *gin.Context) {
var body SecureAccessProfileRequest
err := c.BindJSON(&body)
if err != nil {
log.ErrorLogger("Unable to process json body", err)
errCode := helper.ExtractErrorCode(err)
c.JSON(errCode, gin.H{
"error": strings.Split(err.Error(), " ")[1],
"message": "Unable to process json body",
})
return
}

// Get the application using only the client key
app, err := db.GetApplicationByKey(body.ClientKey)
if err != nil {
log.ErrorLogger("Unable to get application", err)
errCode := helper.ExtractErrorCode(err)
c.JSON(errCode, gin.H{
"error": strings.Split(err.Error(), " ")[1],
"message": "Internal Server Error",
})
c.Abort()
return
}

timestampInt, err := strconv.ParseInt(body.Timestamp, 10, 64)
if err != nil {
log.ErrorLogger("Invalid timestamp", err)
c.JSON(400, gin.H{
"error": "bad_request",
"message": "Invalid timestamp",
})
}

// Validate the signature - this proves the client has the secret without sending it
isValid := helper.ValidateSignature(
body.ClientKey,
app.ClientSecret,
body.RedirectURL,
body.Signature,
timestampInt,
)

if !isValid {
log.ErrorLogger("Invalid signature or expired request", nil)
c.JSON(401, gin.H{
"error": "unauthorized",
"message": "Invalid signature or expired request",
})
return
}

// Check if redirect URL matches
if app.RedirectURL != body.RedirectURL {
log.ErrorLogger("Redirect URL does not match", nil)
c.JSON(400, gin.H{
"error": "bad_request",
"message": "Redirect URL does not match",
})
return
}

c.JSON(http.StatusOK, gin.H{
"message": "Authorized",
})
}
20 changes: 0 additions & 20 deletions api/types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package api

import "time"

type ApplicationPostBody struct {
Name string `json:"name"`
RedirectURL string `json:"redirect_url"`
Expand All @@ -24,21 +22,3 @@ type ApplicationBody struct {
type IdentityBody struct {
Identity string `json:"identity"`
}

type VerifiableIdentityAddress struct {
CreatedAt *time.Time `json:"created_at,omitempty"`
Id *string `json:"id,omitempty"`
Status string `json:"status"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
Value string `json:"value"`
Verified bool `json:"verified"`
VerifiedAt *time.Time `json:"verified_at,omitempty"`
Via string `json:"via"`
}

type SecureAccessProfileRequest struct {
RedirectURL string `json:"redirect_url"`
ClientKey string `json:"client_key"`
Timestamp string `json:"timestamp"` // Unix timestamp to prevent replay attacks
Signature string `json:"signature"` // HMAC signature of "client_key:timestamp:redirect_url"
}
75 changes: 75 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
services:
db:
image: postgres:latest
environment:
POSTGRES_USER: "kratos"
POSTGRES_PASSWORD: "secret"
POSTGRES_DB: "kratos"
volumes:
- db-data:/var/lib/postgresql/data
networks:
- nymeria-network
app:
container_name: nymeria-app
build: .
ports:
- "9898:9898"
depends_on:
- db
- kratos
networks:
- nymeria-network
kratos-migrate:
image: oryd/kratos:v1.3.1
environment:
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc
volumes:
- type: volume
source: kratos-sqlite
target: /var/lib/sqlite
read_only: false
- type: bind
source: ./config
target: /etc/config/kratos
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
restart: on-failure
networks:
- nymeria-network
kratos:
depends_on:
- kratos-migrate
image: oryd/kratos:v1.3.1
restart: unless-stopped
environment:
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
- LOG_LEVEL=trace
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
ports:
- "4433:4433"
- "4434:4434"
volumes:
- type: volume
source: kratos-sqlite
target: /var/lib/sqlite
read_only: false
- type: bind
source: ./config
target: /etc/config/kratos
networks:
- nymeria-network
mailslurper:
image: oryd/mailslurper:latest-smtps
ports:
- "4436:4436"
- "4437:4437"
networks:
- nymeria-network

volumes:
db-data:
name: nymeria-db
kratos-sqlite:

networks:
nymeria-network:
name: nymeria-network
68 changes: 68 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
services:
db:
image: postgres:latest
environment:
POSTGRES_USER: "kratos"
POSTGRES_PASSWORD: "secret"
POSTGRES_DB: "kratos"
volumes:
- db-data:/var/lib/postgresql/data
networks:
- nymeria-network
app:
container_name: nymeria-app
build: .
ports:
- "9898:9898"
depends_on:
- db
- kratos
networks:
- nymeria-network
kratos-migrate:
image: oryd/kratos:v1.3.1
environment:
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true&mode=rwc
volumes:
- type: volume
source: kratos-sqlite
target: /var/lib/sqlite
read_only: false
- type: bind
source: ./config
target: /etc/config/kratos
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
restart: on-failure
networks:
- nymeria-network
kratos:
depends_on:
- kratos-migrate
image: oryd/kratos:v1.3.1
restart: unless-stopped
environment:
- DSN=sqlite:///var/lib/sqlite/db.sqlite?_fk=true
- LOG_LEVEL=trace
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
ports:
- "4433:4433"
- "4434:4434"
volumes:
- type: volume
source: kratos-sqlite
target: /var/lib/sqlite
read_only: false
- type: bind
source: ./config
target: /etc/config/kratos
networks:
- nymeria-network

volumes:
db-data:
name: nymeria-db
kratos-sqlite:

networks:
nymeria-network:
name: nymeria-network
Loading