Skip to content

Commit 636b804

Browse files
Add idp-go-sdk
1 parent e77fbae commit 636b804

14 files changed

Lines changed: 626 additions & 0 deletions

File tree

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ updates:
4545
go-all:
4646
patterns:
4747
- "*"
48+
- package-ecosystem: "gomod"
49+
directory: "/integrations/idp-go-sdk"
50+
schedule:
51+
interval: "daily"
52+
groups:
53+
go-all:
54+
patterns:
55+
- "*"
4856
- package-ecosystem: "gomod"
4957
directory: "/integrations/analytics-go-sdk"
5058
schedule:

.github/workflows/test-integrations.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ on:
99
workflow_dispatch:
1010

1111
jobs:
12+
idp-go-sdk:
13+
name: IDP Go SDK
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Go
20+
uses: actions/setup-go@v5
21+
with:
22+
go-version: 1.26
23+
check-latest: true
24+
cache-dependency-path: "**/*.sum"
25+
26+
- name: Install dependencies for go-sdk
27+
run: go work sync
28+
29+
- name: Test go-sdk
30+
run: go test ./integrations/idp-go-sdk/... -v
31+
1232
analytics-go-sdk:
1333
name: Analytics Go SDK
1434
runs-on: ubuntu-latest

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ go 1.26
22

33
use (
44
integrations/analytics-go-sdk
5+
integrations/idp-go-sdk
56
integrations/storage-go-sdk
67
services/core
78
services/storage

integrations/idp-go-sdk/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Oliver Schlüter
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

integrations/idp-go-sdk/go.mod

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module github.com/fancyinnovations/fancyspaces/integrations/idp-go-sdk
2+
3+
go 1.26
4+
5+
require (
6+
github.com/OliverSchlueter/goutils v0.0.28
7+
github.com/dgraph-io/ristretto/v2 v2.4.0
8+
github.com/golang-jwt/jwt/v5 v5.3.1
9+
)
10+
11+
require (
12+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
13+
github.com/dustin/go-humanize v1.0.1 // indirect
14+
github.com/klauspost/compress v1.18.3 // indirect
15+
github.com/nats-io/nats.go v1.48.0 // indirect
16+
github.com/nats-io/nkeys v0.4.12 // indirect
17+
github.com/nats-io/nuid v1.0.1 // indirect
18+
golang.org/x/crypto v0.47.0 // indirect
19+
golang.org/x/sys v0.40.0 // indirect
20+
)

integrations/idp-go-sdk/go.sum

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
github.com/OliverSchlueter/goutils v0.0.28 h1:Ayj+cwmryXZ8651KWGzH8HAmjlSbyoTmUSoewF3tCDk=
2+
github.com/OliverSchlueter/goutils v0.0.28/go.mod h1:iyXl5/swm34WrhnD2pHxA4X1PH61bN2O63qGAP9j2qA=
3+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
4+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7+
github.com/dgraph-io/ristretto/v2 v2.4.0 h1:I/w09yLjhdcVD2QV192UJcq8dPBaAJb9pOuMyNy0XlU=
8+
github.com/dgraph-io/ristretto/v2 v2.4.0/go.mod h1:0KsrXtXvnv0EqnzyowllbVJB8yBonswa2lTCK2gGo9E=
9+
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
10+
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
11+
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
12+
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
13+
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
14+
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
15+
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
16+
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
17+
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
18+
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
19+
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
20+
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
21+
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
22+
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
23+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
24+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
25+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
26+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
27+
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
28+
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
29+
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
30+
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
31+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
32+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package idp
2+
3+
import (
4+
"crypto/rsa"
5+
6+
"github.com/OliverSchlueter/goutils/broker"
7+
)
8+
9+
type Service struct {
10+
broker broker.Broker
11+
excludedRoutes []string
12+
publicKey *rsa.PublicKey
13+
usersCache *usersCache
14+
}
15+
16+
type Configuration struct {
17+
PublicKey *rsa.PublicKey
18+
Broker broker.Broker
19+
ExcludedRoutes []string
20+
}
21+
22+
func NewService(cfg Configuration) *Service {
23+
return &Service{
24+
broker: cfg.Broker,
25+
excludedRoutes: cfg.ExcludedRoutes,
26+
publicKey: cfg.PublicKey,
27+
usersCache: newUsersCache(),
28+
}
29+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package idp
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/golang-jwt/jwt/v5"
7+
)
8+
9+
const ServiceName = "fancyanalytics-idp"
10+
11+
var SigningMethod = jwt.SigningMethodRS256
12+
13+
// validateToken validates the given JWT token string and returns the user ID if the token is valid.
14+
func (s *Service) validateToken(tokenString string) (string, error) {
15+
parser := jwt.NewParser(
16+
jwt.WithValidMethods([]string{SigningMethod.Alg()}),
17+
jwt.WithIssuer(ServiceName),
18+
)
19+
20+
token, err := parser.ParseWithClaims(
21+
tokenString,
22+
&jwt.RegisteredClaims{},
23+
s.tokenKeyFunc,
24+
)
25+
if err != nil {
26+
return "", fmt.Errorf("failed to parse token: %w", err)
27+
}
28+
29+
claims, ok := token.Claims.(*jwt.RegisteredClaims)
30+
if !ok || !token.Valid {
31+
return "", ErrInvalidToken
32+
}
33+
34+
if claims.Issuer != ServiceName {
35+
return "", ErrInvalidToken
36+
}
37+
38+
return claims.Subject, nil
39+
}
40+
41+
// tokenKeyFunc is a helper function that returns the public key for validating the token's signature.
42+
func (s *Service) tokenKeyFunc(token *jwt.Token) (interface{}, error) {
43+
if token.Method.Alg() != SigningMethod.Alg() {
44+
return nil, fmt.Errorf("unexpected signing method")
45+
}
46+
47+
return s.publicKey, nil
48+
}

integrations/idp-go-sdk/idp/ctx.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package idp
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
)
7+
8+
type userCtxKey struct{}
9+
10+
// attachUserToCtx attaches the user to the context for later retrieval in handlers.
11+
func attachUserToCtx(ctx context.Context, u *User) context.Context {
12+
return context.WithValue(ctx, userCtxKey{}, *u)
13+
}
14+
15+
// UserFromCtx retrieves the user from the context. It returns nil if no user is found.
16+
func UserFromCtx(ctx context.Context) *User {
17+
u, ok := ctx.Value(userCtxKey{}).(User)
18+
if !ok {
19+
slog.Warn("User not found in context")
20+
return nil
21+
}
22+
return &u
23+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package idp
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"time"
7+
8+
"github.com/OliverSchlueter/goutils/problems"
9+
)
10+
11+
var (
12+
ErrMissingAuthorizationHeader = errors.New("missing Authorization header")
13+
ErrInvalidTokenFormat = errors.New("invalid token format")
14+
ErrInvalidAuthenticationMethod = errors.New("invalid authentication method, expected Bearer or Basic")
15+
ErrInvalidToken = errors.New("invalid token")
16+
ErrUserNotFound = errors.New("user not found")
17+
)
18+
19+
func AccountNotVerifiedProblem() *problems.Problem {
20+
return &problems.Problem{
21+
Type: "AccountNotVerified",
22+
Title: "Account Not Verified",
23+
Detail: "Your account is not verified. Please verify your account to access this feature.",
24+
Status: http.StatusForbidden,
25+
Timestamp: time.Now(),
26+
}
27+
}
28+
29+
func AccountDisabledProblem() *problems.Problem {
30+
return &problems.Problem{
31+
Type: "AccountDisabled",
32+
Title: "Account Disabled",
33+
Detail: "Your account has been disabled. Please contact support for assistance.",
34+
Status: http.StatusForbidden,
35+
Timestamp: time.Now(),
36+
}
37+
}

0 commit comments

Comments
 (0)