Skip to content

Commit

Permalink
Merge pull request #104 from carverauto/102-new-tenant
Browse files Browse the repository at this point in the history
102 new tenant
  • Loading branch information
mfreeman451 authored Apr 27, 2024
2 parents 7cfc21b + e2d9392 commit 6bbb206
Show file tree
Hide file tree
Showing 31 changed files with 299 additions and 50 deletions.
16 changes: 8 additions & 8 deletions bots/IRC/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package main
import (
"context"
"encoding/json"
"github.com/carverauto/threadr/bots/pkg/adapters/broker"
irc "github.com/carverauto/threadr/bots/pkg/adapters/messages"
"github.com/carverauto/threadr/bots/pkg/common"
pm "github.com/carverauto/threadr/bots/pkg/ports"
"github.com/carverauto/threadr/pkg/adapters/broker"
irc "github.com/carverauto/threadr/pkg/adapters/messages"
"github.com/carverauto/threadr/pkg/chat"
pm "github.com/carverauto/threadr/pkg/ports"
"github.com/nats-io/nats.go"
"log"
"time"
)

func main() {
natsURL := "nats://nats.nats.svc.cluster.local:4222"
sendSubject := "irc"
sendSubject := "chat"
stream := "messages"
cmdsSubject := "incoming"
cmdsStream := "commands"
Expand Down Expand Up @@ -53,7 +53,7 @@ func main() {
log.Println("main.go - Subscribing to results")
resultsHandler.Listen(resultsSubject, "results-durable", func(msg *nats.Msg) {
log.Printf("main.go - Received result: %s", string(msg.Data))
var result common.CommandResult
var result chat.CommandResult
if err := json.Unmarshal(msg.Data, &result); err != nil {
log.Printf("main.go - Failed to unmarshal result: %s", err)
return
Expand All @@ -73,7 +73,7 @@ func main() {

// start a counter for received message_processing
msgCounter := 0
ircAdapter.Listen(func(ircMsg common.IRCMessage) {
ircAdapter.Listen(func(ircMsg chat.IRCMessage) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

Expand All @@ -88,7 +88,7 @@ func main() {

// Publish the CloudEvent
log.Printf("main.go - Publishing CloudEvent for message [%d]", msgCounter)
err := cloudEventsHandler.PublishEvent(ctx, "irc", ce)
err := cloudEventsHandler.PublishEvent(ctx, "chat", ce)
if err != nil {
log.Printf("Failed to send CloudEvent: %v", err)
} else {
Expand Down
12 changes: 6 additions & 6 deletions bots/discord/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/carverauto/threadr/bots/pkg/adapters/broker"
d "github.com/carverauto/threadr/bots/pkg/adapters/messages"
"github.com/carverauto/threadr/bots/pkg/common"
"github.com/carverauto/threadr/pkg/adapters/broker"
d "github.com/carverauto/threadr/pkg/adapters/messages"
"github.com/carverauto/threadr/pkg/chat"
"github.com/nats-io/nats.go"
"log"
"os"
Expand All @@ -16,7 +16,7 @@ import (
func main() {
// natsURL := "nats://nats.nats.svc.cluster.local:4222"
natsURL := os.Getenv("NATSURL")
sendSubject := "irc"
sendSubject := "chat"
stream := "messages"
// cmdsSubject := "incoming"
// cmdsStream := "commands"
Expand All @@ -31,7 +31,7 @@ func main() {
}

/*
commandsHandler, err := broker.NewCloudEventsNATSHandler(natsURL, cmdsSubject, cmdsStream, false)
commandsHandler, err := nats.NewCloudEventsNATSHandler(natsURL, cmdsSubject, cmdsStream, false)
if err != nil {
log.Fatalf("Failed to create CloudEvents handler: %s", err)
Expand All @@ -55,7 +55,7 @@ func main() {
log.Println("Subscribing to results")
resultsHandler.Listen(resultsSubject, "results-durable", func(msg *nats.Msg) {
log.Printf("Received result: %s", string(msg.Data))
var result common.CommandResult
var result chat.CommandResult
if err := json.Unmarshal(msg.Data, &result); err != nil {
log.Printf("Failed to unmarshal result: %v", err)
return
Expand Down
8 changes: 3 additions & 5 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ func main() {

FirebaseApp, err := firebase.SetupFirebaseApp(ctx)
if err != nil {
log.Println("Error initializing Firebase App:", err)
return
log.Fatalf("Error initializing Firebase App: %v", err)
}

// Group for routes that require tenant and role verification
// Group for routes that require instance and role verification
secure := app.Group("/secure")
routes.SetupSecureRoutes(secure, FirebaseApp)

Expand All @@ -40,7 +39,6 @@ func main() {

fErr := app.Listen(":3001")
if fErr != nil {
log.Println("Error starting the server:", fErr)
return
log.Fatalf("Error starting server: %v", fErr)
}
}
4 changes: 4 additions & 0 deletions cmd/client/admin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Client/Admin

This is the admin client for threadr and is used to assign admin
roles to threadr admins.
8 changes: 4 additions & 4 deletions cmd/client/admin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func main() {
// Define the user and claims you want to set
userID := os.Getenv("FIREBASE_USER_ID")
userClaims := map[string]string{
"role": "admin",
"tenantId": "threadr",
"role": "admin",
"instanceId": "threadr",
}

err := SendClaimsUpdate(setClaimsURL, apiKey, userID, userClaims)
Expand All @@ -46,8 +46,8 @@ func main() {
}

// Test the secure endpoint that requires tenant ID in the URL
log.Printf("Accessing secure tenant endpoint for tenant: %s\n", userClaims["tenantId"])
if err := AccessSecureEndpoint(fmt.Sprintf(secureEndpoint, userClaims["tenantId"]), apiKey); err != nil {
log.Printf("Accessing secure instance endpoint for instance: %s\n", userClaims["instanceId"])
if err := AccessSecureEndpoint(fmt.Sprintf(secureEndpoint, userClaims["instanceId"]), apiKey); err != nil {
fmt.Println("Error accessing secure endpoint:", err)
}
}
6 changes: 3 additions & 3 deletions k8s/threadr/base/threadr-consumer-irc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ spec:
labels:
app: threadr-consumer-irc
spec:
serviceAccountName: threadr-irc-account
serviceAccountName: threadr-chat-account
imagePullSecrets:
- name: ghcr-io-cred
containers:
- name: threadr-consumer-irc
image: ghcr.io/carverauto/threadr-consumer-irc:v0.0.5
- name: threadr-consumer-chat
image: ghcr.io/carverauto/threadr-consumer-chat:v0.0.5
imagePullPolicy: Always
env:
- name: NATSURL
Expand Down
2 changes: 1 addition & 1 deletion k8s/threadr/base/threadr-embeds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ spec:
labels:
app: threadr-embeds
spec:
serviceAccountName: threadr-irc-account
serviceAccountName: threadr-chat-account
imagePullSecrets:
- name: ghcr-io-cred
containers:
Expand Down
2 changes: 1 addition & 1 deletion k8s/threadr/base/threadr-irc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ spec:
labels:
app: threadr-irc
spec:
serviceAccountName: threadr-irc-account
serviceAccountName: threadr-chat-account
imagePullSecrets:
- name: ghcr-io-cred
containers:
Expand Down
2 changes: 1 addition & 1 deletion k8s/threadr/base/threadr-messages.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ spec:
labels:
app: threadr-messages
spec:
serviceAccountName: threadr-irc-account
serviceAccountName: threadr-chat-account
imagePullSecrets:
- name: ghcr-io-cred
containers:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package broker pkg/adapters/broker/nats_adapter.go
// Package nats pkg/adapters/nats/nats_adapter.go
// provides a CloudEventsNATSHandler that can be used to publish and subscribe to CloudEvents messages.

package broker
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package messages ./bots/IRC/pkg/adapters/message_processing/irc.go
// Package messages ./bots/IRC/pkg/adapters/message_processing/chat.go
package messages

import (
Expand All @@ -22,7 +22,7 @@ type IRCAdapter struct {

type IRCAdapterConfig struct {
Nick string `envconfig:"BOT_NICK" default:"threadr" required:"true"`
Server string `envconfig:"BOT_SERVER" default:"irc.choopa.net:6667" required:"true"`
Server string `envconfig:"BOT_SERVER" default:"chat.choopa.net:6667" required:"true"`
Channels string `envconfig:"BOT_CHANNELS" default:"#!chases,#chases,#𝓉𝓌𝑒𝓇𝓀𝒾𝓃,#singularity" required:"true"`
BotSaslLogin string `envconfig:"BOT_SASL_LOGIN"`
BotSaslPassword string `envconfig:"BOT_SASL_PASSWORD"`
Expand Down Expand Up @@ -75,7 +75,7 @@ func (irc *IRCAdapter) Connect(ctx context.Context, commandEventsHandler *broker
target, message := e.Params[0], e.Params[1]
log.Println("PRIVMSG", target, message)
if strings.HasPrefix(message, irc.Connection.Nick+":") {
// irc.Connection.Privmsg(target, "I'm a simple IRC bot.")
// chat.Connection.Privmsg(target, "I'm a simple IRC bot.")
command := strings.TrimSpace(strings.TrimPrefix(message, irc.Connection.Nick+":"))
log.Println("Command:", command)
// ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
Expand Down
File renamed without changes.
File renamed without changes.
32 changes: 32 additions & 0 deletions pkg/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ThreadR API

## New Users

```mermaid
flowchart TD
A[User Signs Up] --> B{Firebase Auth}
B --> C[User Added to Firebase under no tenant]
C --> D[User logs into Dashboard]
D --> E{User Belongs to a Tenant?}
E -->|No| F[User can create or wait for an invitation to join a Tenant]
E -->|Yes| G[Access Tenant-Specific Features]
F --> H[User Creates a Tenant]
F --> I[User Receives an Invitation]
H --> J[User Joins Own Tenant]
I --> J
J --> G
### Explanation of Each Step:
- **A (User Signs Up)**: The user initiates the process by signing up through the app's registration interface.
- **B (Firebase Auth)**: The user's information is processed through Firebase Authentication.
- **C (User Added to Firebase under no tenant)**: Once authenticated, the user is added to Firebase with no specific tenant associated.
- **D (User logs into Dashboard)**: Post-registration, the user logs in and accesses the main dashboard.
- **E (User Belongs to a Tenant?)**: Check if the user is associated with any tenant.
- **F (User can create or wait for an invitation to join a Tenant)**: If not part of a tenant, the user has the option to create a new tenant or wait to be invited to one.
- **G (Access Tenant-Specific Features)**: If part of a tenant, the user can access features specific to that tenant.
- **H (User Creates a Tenant)**: The user decides to create a new tenant.
- **I (User Receives an Invitation)**: Alternatively, the user may receive an invitation to join an existing tenant.
- **J (User Joins Own Tenant or the Invited Tenant)**: The user joins either the tenant they created or the one they were invited to.
This flowchart helps in visualizing the step-by-step process of user onboarding and tenant association in your application, making it clear how new users are handled from signup to accessing tenant-specific functionalities.
3 changes: 2 additions & 1 deletion pkg/api/handlers/custom_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ func GetCustomClaimsHandler(app *firebase.App) fiber.Handler {
fmt.Println("Looking up user:", userId)
user, err := authClient.GetUser(ctx, userId)
if err != nil {
log.Fatal(err)
log.Println("Failed to get user:", err)
return c.Status(fiber.StatusInternalServerError).SendString("Failed to get user")
}
// The claims can be accessed on the user record.
if admin, ok := user.CustomClaims["admin"]; ok {
Expand Down
39 changes: 39 additions & 0 deletions pkg/api/middleware/admin_auth.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package middleware

import (
firebase "firebase.google.com/go"
"github.com/gofiber/fiber/v2"
"log"
"os"
)

Expand All @@ -15,3 +17,40 @@ func ApiKeyMiddleware() fiber.Handler {
return c.Next()
}
}

// SuperAdminAuthMiddleware checks to see if the user has the super admin claim set and is instance admin.
func SuperAdminAuthMiddleware(FirebaseApp *firebase.App) fiber.Handler {
return func(c *fiber.Ctx) error {
// get context from fiber.Ctx
ctx := c.Context()

authClient, err := FirebaseApp.Auth(ctx)
if err != nil {
log.Println("Failed to initialize Firebase Auth client:", err)
return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
}

idToken := c.Get("Authorization")
token, err := authClient.VerifyIDToken(ctx, idToken)
if err != nil {
log.Printf("Token verification failed: %v", err)
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized access - Invalid token")
}

claims := token.Claims
expectedInstanceId := c.Params("instance") // Retrieve instance ID from the URL parameter

role, hasRole := claims["role"].(string)
instanceId, hasInstanceId := claims["InstanceId"].(string)
if !hasRole || role != "super" {
log.Printf("Access denied: user role '%v' is not 'admin'", role)
return c.Status(fiber.StatusForbidden).SendString("Access denied - You must be an admin")
}
if !hasInstanceId || instanceId != expectedInstanceId {
log.Printf("Access denied: user's instanceID '%v' does not match expected '%v'", instanceId, expectedInstanceId)
return c.Status(fiber.StatusForbidden).SendString("Access denied - Invalid instance access")
}

return c.Next()
}
}
14 changes: 7 additions & 7 deletions pkg/api/middleware/tenant.go → pkg/api/middleware/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"log"
)

// RoleTenantMiddleware checks if the user is an admin and if they belong to the tenant specified in the URL.
func RoleTenantMiddleware(FirebaseApp *firebase.App) fiber.Handler {
// RoleInstanceMiddleware checks if the user is an admin and if they belong to the instance specified in the URL.
func RoleInstanceMiddleware(FirebaseApp *firebase.App) fiber.Handler {
return func(c *fiber.Ctx) error {
authClient, err := FirebaseApp.Auth(context.Background())
if err != nil {
Expand All @@ -24,17 +24,17 @@ func RoleTenantMiddleware(FirebaseApp *firebase.App) fiber.Handler {
}

claims := token.Claims
expectedTenantId := c.Params("tenant") // Retrieve tenant ID from the URL parameter
expectedInstanceId := c.Params("instance") // Retrieve instance ID from the URL parameter

role, hasRole := claims["role"].(string)
tenantId, hasTenantId := claims["tenantId"].(string)
instanceId, hasInstanceId := claims["instanceId"].(string)
if !hasRole || role != "admin" {
log.Printf("Access denied: user role '%v' is not 'admin'", role)
return c.Status(fiber.StatusForbidden).SendString("Access denied - You must be an admin")
}
if !hasTenantId || tenantId != expectedTenantId {
log.Printf("Access denied: user's tenantId '%v' does not match expected '%v'", tenantId, expectedTenantId)
return c.Status(fiber.StatusForbidden).SendString("Access denied - Invalid tenant access")
if !hasInstanceId || instanceId != expectedInstanceId {
log.Printf("Access denied: user's instanceId '%v' does not match expected '%v'", instanceId, expectedInstanceId)
return c.Status(fiber.StatusForbidden).SendString("Access denied - Invalid instance access")
}

return c.Next()
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/nats/nats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nats

func CreateNATSUser() {

}
6 changes: 3 additions & 3 deletions pkg/api/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ func SetupRoutes(app *fiber.App, FirebaseApp *firebase.App) {

// SetupSecureRoutes initializes the routes that require role and tenant level security.
func SetupSecureRoutes(secure fiber.Router, FirebaseApp *firebase.App) {
secure.Use(middleware.RoleTenantMiddleware(FirebaseApp)) // Apply the middleware to all routes under /secure
secure.Get("/:tenant", func(c *fiber.Ctx) error { // Tenant-specific route
secure.Use(middleware.RoleInstanceMiddleware(FirebaseApp)) // Apply the middleware to all routes under /secure
secure.Get("/:instance", func(c *fiber.Ctx) error { // Instance-specific route
userClaims := c.Locals("user").(map[string]interface{})
return c.SendString("Welcome Admin, Tenant ID: " + userClaims["tenantId"].(string))
return c.SendString("Welcome Admin, Instance ID: " + userClaims["instanceId"].(string))
})
}
Loading

0 comments on commit 6bbb206

Please sign in to comment.