Skip to content

Commit a85f35a

Browse files
authored
Merge pull request #13 from bat-bs/reporting
Feat: Add Admin Panel
2 parents 5ff990c + d118915 commit a85f35a

15 files changed

+391
-64
lines changed

api/apiserver.go

+73
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import (
1212

1313
func ApiInit(mux *http.ServeMux, a *auth.Auth) {
1414
api := ApiHandler{db.NewDB(), a}
15+
mux.HandleFunc("/api2/user/widget", api.GetUserWidget)
16+
mux.HandleFunc("/api2/user/logout", api.LogoutUser)
17+
mux.HandleFunc("/api2/admin/table/get", api.GetAdminTable)
1518
mux.HandleFunc("/api2/table/get", api.GetTable)
1619
mux.HandleFunc("/api2/table/entry/save", api.CreateEntry)
1720
mux.HandleFunc("/api2/table/entry/delete/", api.DeleteEntry)
@@ -23,6 +26,61 @@ type ApiHandler struct {
2326
auth *auth.Auth
2427
}
2528

29+
func (a *ApiHandler) GetAdminTable(w http.ResponseWriter, r *http.Request) {
30+
ok, err := a.auth.ValidateAdminSession(w, r)
31+
if err == nil && ok {
32+
keys, err := a.db.LookupApiKeyUserOverview()
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
templ := template.Must(template.New("adminTable.html.templ").Funcs(sprig.FuncMap()).ParseFiles("templates/adminTable.html.templ"))
37+
38+
if err != nil {
39+
panic(err)
40+
}
41+
42+
err = templ.Execute(w, keys)
43+
if err != nil {
44+
panic(err)
45+
}
46+
} else {
47+
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
48+
}
49+
}
50+
51+
func (a *ApiHandler) LogoutUser(w http.ResponseWriter, r *http.Request) {
52+
a.auth.LogoutHandler(w, r)
53+
54+
}
55+
56+
func (a *ApiHandler) GetUserWidget(w http.ResponseWriter, r *http.Request) {
57+
58+
claims, err := a.auth.GetClaims(r)
59+
if err != nil {
60+
http.Error(w, "Not Authenticated", http.StatusForbidden)
61+
return
62+
}
63+
claimsDB, err := a.db.GetUser(claims.Sub)
64+
if err != nil {
65+
log.Println("Could not get Claims from DB")
66+
return
67+
}
68+
userWidgetContent := `
69+
<p class="text-xl">{{if .IsAdmin }}<a class="text-sky-400" href="/admin">Admin-Panel</a> - <a class="text-sky-400" href="">Homepage</a>{{ end }} </p><p>Hallo, {{.Name}} <u><a class="text-sky-400" href="/api2/user/logout">logout</a></u></p>
70+
71+
`
72+
userWidget, err := template.New(userWidgetContent).Parse(userWidgetContent)
73+
if err != nil {
74+
log.Println("Error while templating the pop up")
75+
return
76+
}
77+
78+
err = userWidget.Execute(w, claimsDB)
79+
if err != nil {
80+
log.Println("Error while filling out the Template or writing Response")
81+
}
82+
}
83+
2684
func (a *ApiHandler) GetTable(w http.ResponseWriter, r *http.Request) {
2785
// Check if Request is Authenticated
2886
if !a.auth.ValidateSessionToken(w, r) {
@@ -34,6 +92,21 @@ func (a *ApiHandler) GetTable(w http.ResponseWriter, r *http.Request) {
3492
// If Claims Extraction Failed, user will be Redirected to Update his Token.
3593
a.Unauthenticated(w, r)
3694
}
95+
admin := false
96+
log.Println(claims.Roles)
97+
for _, group := range claims.Roles {
98+
if group == "admin" {
99+
admin = true
100+
}
101+
}
102+
// Check/Create user as its the first authenticated action ran
103+
u := db.User{
104+
Name: claims.Name,
105+
Sub: claims.Sub,
106+
IsAdmin: admin,
107+
}
108+
a.db.WriteUser(&u)
109+
37110
keys, err := a.db.LookupApiKeyInfos(claims.Sub)
38111
if err != nil {
39112
log.Fatal(err)

api/entry.go

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ func (a *ApiHandler) CreateEntry(w http.ResponseWriter, r *http.Request) {
4949
UUID: uuid,
5050
ApiKey: a.hashToken(apikey),
5151
Owner: claims.Sub,
52-
Groups: claims.Groups,
5352
AiApi: r.Form.Get("apitype"),
5453
Description: r.Form.Get("beschreibung"),
5554
}

apiproxy/response.go

+2
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ func NewResponse(in *http.Response) error {
3333
rs: in,
3434
db: db.NewDB(),
3535
}
36+
3637
err := r.ReadValues()
3738
if err != nil {
3839
return err
3940
}
4041
go r.ProcessValues()
42+
defer r.db.Close()
4143
return nil
4244
}
4345

apiproxy/validateToken.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func ValidateToken(w http.ResponseWriter, r *http.Request) string {
3636
}
3737

3838
db := db.NewDB()
39+
defer db.Close()
3940

4041
hashes, err := db.LookupApiKeys("*")
4142
if err != nil || len(hashes) == 0 {

auth/auth.go

+83-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import (
44
"context"
55
"crypto/rand"
66
"encoding/base64"
7+
"fmt"
78
"log"
89
"net/http"
10+
"net/url"
911
"os"
1012
"time"
1113

14+
db "openai-api-proxy/db"
15+
1216
"github.com/coreos/go-oidc/v3/oidc"
1317
"golang.org/x/oauth2"
1418
)
@@ -25,6 +29,11 @@ func Init(mux *http.ServeMux) (a *Auth) {
2529
// handle error
2630
log.Println("Cannot Initiate Provider: ", err)
2731
}
32+
var claims *ProviderClaims
33+
err = provider.Claims(&claims)
34+
if err != nil {
35+
log.Println("Cannot extract provider Claims for LogoutURL", err)
36+
}
2837

2938
clientId, ok := os.LookupEnv("CLIENT_ID")
3039
if !ok {
@@ -50,33 +59,65 @@ func Init(mux *http.ServeMux) (a *Auth) {
5059
Endpoint: provider.Endpoint(),
5160

5261
// "openid" is a required scope for OpenID Connect flows.
53-
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
62+
Scopes: []string{oidc.ScopeOpenID, "profile", "email", "roles"},
5463
}
5564

5665
var verifier = provider.Verifier(&oidc.Config{ClientID: clientId})
5766
a = &Auth{
5867
oauth2Config: oauth2Config,
5968
ctx: context.Background(),
6069
verifier: verifier,
70+
provider: provider,
71+
claims: claims,
6172
}
6273
mux.HandleFunc("/callback/", a.CallbackHandler)
6374
mux.HandleFunc("/login/", a.LoginHandler)
6475
return a
6576
}
6677

78+
type ProviderClaims struct {
79+
EndSessionURL string `json:"end_session_endpoint"`
80+
}
81+
6782
type Claims struct {
6883
Email string `json:"email"`
84+
Name string `json:"name"`
6985
Verified bool `json:"email_verified"`
7086
Sub string `json:"sub"`
71-
Groups []string `json:"groups"`
87+
Roles []string `json:"roles"`
7288
}
7389

7490
type Auth struct {
7591
oauth2Config *oauth2.Config
7692
ctx context.Context
7793
verifier *oidc.IDTokenVerifier
94+
provider *oidc.Provider
95+
claims *ProviderClaims
7896
}
7997

98+
func (a *Auth) LogoutHandler(w http.ResponseWriter, r *http.Request) {
99+
state, _ := r.Cookie("oauthstate")
100+
http.SetCookie(w, &http.Cookie{
101+
Name: "session_token",
102+
Path: "/",
103+
Value: "",
104+
Expires: time.Unix(0, 0),
105+
})
106+
if a.claims.EndSessionURL == "" {
107+
http.Error(w, "Logout not implemented by Identity Provider", http.StatusNotImplemented)
108+
return
109+
}
110+
logoutURL, err := url.Parse(a.claims.EndSessionURL)
111+
if err != nil {
112+
log.Println("Error parsing URL: ", err)
113+
}
114+
query := logoutURL.Query()
115+
query.Set("state", state.Value)
116+
query.Set("post_logout_redirect_uri", a.oauth2Config.RedirectURL) // Not implemented by our IDP of Testing https://github.com/zitadel/zitadel/issues/6615
117+
logoutURL.RawQuery = query.Encode()
118+
119+
http.Redirect(w, r, logoutURL.String(), http.StatusTemporaryRedirect)
120+
}
80121
func (a *Auth) LoginHandler(w http.ResponseWriter, r *http.Request) {
81122

82123
// Create oauthState cookie
@@ -127,16 +168,55 @@ func (a *Auth) GetClaims(r *http.Request) (*Claims, error) {
127168
var err error
128169
// Parse and verify ID Token payload.
129170
rawIDToken, err := r.Cookie("session_token")
171+
if err != nil {
172+
return nil, err
173+
}
130174
idToken, err := a.verifier.Verify(a.ctx, rawIDToken.Value)
175+
if err != nil {
176+
return nil, err
177+
}
131178
claims := &Claims{}
132179
// Extract custom claims
133-
if err = idToken.Claims(claims); err != nil {
180+
181+
if err = idToken.Claims(&claims); err != nil {
134182
log.Println("Error Extracting User-Claim", err)
135183
return nil, err
136184
// handle error
137185
}
138186
return claims, nil
139187
}
188+
189+
type NotAdmin struct {
190+
uid string
191+
}
192+
193+
func (n *NotAdmin) Error() string {
194+
return fmt.Sprintf("User %s has no Admin Role", n.uid)
195+
}
196+
197+
func (a *Auth) ValidateAdminSession(w http.ResponseWriter, r *http.Request) (bool, error) {
198+
authenticated := a.ValidateSessionToken(w, r)
199+
if !authenticated {
200+
log.Println("Not Authenticated")
201+
return false, nil
202+
}
203+
claims, err := a.GetClaims(r)
204+
if err != nil {
205+
log.Println("Claims not found")
206+
return false, err
207+
}
208+
db := db.NewDB()
209+
defer db.Close()
210+
user, err := db.GetUser(claims.Sub)
211+
if err != nil {
212+
return false, err
213+
}
214+
if user.IsAdmin {
215+
return true, nil
216+
}
217+
return false, &NotAdmin{uid: claims.Sub}
218+
}
219+
140220
func (a *Auth) ValidateSessionToken(w http.ResponseWriter, r *http.Request) bool {
141221
// Parse and verify ID Token payload.
142222
rawIDToken, err := r.Cookie("session_token")

0 commit comments

Comments
 (0)