Compare commits
8 Commits
1765485027
...
a773f1f8b4
Author | SHA1 | Date | |
---|---|---|---|
a773f1f8b4 | |||
1a71f50914 | |||
d17e154e42 | |||
bad26775eb | |||
c3fd6637a5 | |||
20173ea140 | |||
41d439beab | |||
b36b6e18ca |
@ -20,21 +20,7 @@ type AuthHandler struct {
|
|||||||
cfg *config.AppConfig
|
cfg *config.AppConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignedToken struct {
|
func (h *AuthHandler) signTokens(user *repository.User) (*types.SignedToken, *types.SignedToken, error) {
|
||||||
Token string
|
|
||||||
ExpiresAt time.Time
|
|
||||||
ID uuid.UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSignedToken(token string, expiresAt time.Time, jti uuid.UUID) *SignedToken {
|
|
||||||
return &SignedToken{
|
|
||||||
Token: token,
|
|
||||||
ExpiresAt: expiresAt,
|
|
||||||
ID: jti,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *AuthHandler) signTokens(user *repository.User) (*SignedToken, *SignedToken, error) {
|
|
||||||
accessExpiresAt := time.Now().Add(15 * time.Minute)
|
accessExpiresAt := time.Now().Add(15 * time.Minute)
|
||||||
accessJTI := uuid.New()
|
accessJTI := uuid.New()
|
||||||
|
|
||||||
@ -75,7 +61,7 @@ func (h *AuthHandler) signTokens(user *repository.User) (*SignedToken, *SignedTo
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewSignedToken(accessToken, accessExpiresAt, accessJTI), NewSignedToken(refreshToken, refreshExpiresAt, refreshJTI), nil
|
return types.NewSignedToken(accessToken, accessExpiresAt, accessJTI), types.NewSignedToken(refreshToken, refreshExpiresAt, refreshJTI), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthHandler(repo *repository.Queries, cache *cache.Client, cfg *config.AppConfig) *AuthHandler {
|
func NewAuthHandler(repo *repository.Queries, cache *cache.Client, cfg *config.AppConfig) *AuthHandler {
|
||||||
|
@ -17,20 +17,10 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApiToken struct {
|
func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *repository.ApiService, nonce *string) (*types.SignedToken, *types.SignedToken, *types.SignedToken, error) {
|
||||||
Token string
|
|
||||||
Expiration float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type ApiTokens struct {
|
|
||||||
ID ApiToken
|
|
||||||
Access ApiToken
|
|
||||||
Refresh ApiToken
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *repository.ApiService, nonce *string) (*ApiTokens, error) {
|
|
||||||
accessExpiresIn := 15 * time.Minute
|
accessExpiresIn := 15 * time.Minute
|
||||||
accessExpiresAt := time.Now().Add(accessExpiresIn)
|
accessExpiresAt := time.Now().Add(accessExpiresIn)
|
||||||
|
accessJTI := uuid.New()
|
||||||
|
|
||||||
accessClaims := types.ApiClaims{
|
accessClaims := types.ApiClaims{
|
||||||
Permissions: []string{},
|
Permissions: []string{},
|
||||||
@ -40,12 +30,13 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
|
|||||||
Audience: jwt.ClaimStrings{apiService.ClientID},
|
Audience: jwt.ClaimStrings{apiService.ClientID},
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
ExpiresAt: jwt.NewNumericDate(accessExpiresAt),
|
ExpiresAt: jwt.NewNumericDate(accessExpiresAt),
|
||||||
|
ID: accessJTI.String(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
access, err := util.SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey)
|
access, err := util.SignJwtToken(accessClaims, h.cfg.Jwt.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var roles = []string{"user"}
|
var roles = []string{"user"}
|
||||||
@ -56,6 +47,7 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
|
|||||||
|
|
||||||
idExpiresIn := 15 * time.Minute
|
idExpiresIn := 15 * time.Minute
|
||||||
idExpiresAt := time.Now().Add(idExpiresIn)
|
idExpiresAt := time.Now().Add(idExpiresIn)
|
||||||
|
idJTI := uuid.New()
|
||||||
|
|
||||||
idClaims := types.IdTokenClaims{
|
idClaims := types.IdTokenClaims{
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
@ -70,16 +62,18 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
|
|||||||
Audience: jwt.ClaimStrings{apiService.ClientID},
|
Audience: jwt.ClaimStrings{apiService.ClientID},
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
ExpiresAt: jwt.NewNumericDate(idExpiresAt),
|
ExpiresAt: jwt.NewNumericDate(idExpiresAt),
|
||||||
|
ID: idJTI.String(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
idToken, err := util.SignJwtToken(idClaims, h.cfg.Jwt.PrivateKey)
|
idToken, err := util.SignJwtToken(idClaims, h.cfg.Jwt.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshExpiresIn := 24 * time.Hour
|
refreshExpiresIn := 24 * time.Hour
|
||||||
refreshExpiresAt := time.Now().Add(refreshExpiresIn)
|
refreshExpiresAt := time.Now().Add(refreshExpiresIn)
|
||||||
|
refreshJTI := uuid.New()
|
||||||
|
|
||||||
refreshClaims := types.ApiRefreshClaims{
|
refreshClaims := types.ApiRefreshClaims{
|
||||||
UserID: user.ID.String(),
|
UserID: user.ID.String(),
|
||||||
@ -89,28 +83,16 @@ func (h *OAuthHandler) signApiTokens(user *repository.User, apiService *reposito
|
|||||||
Audience: jwt.ClaimStrings{apiService.ClientID},
|
Audience: jwt.ClaimStrings{apiService.ClientID},
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
ExpiresAt: jwt.NewNumericDate(refreshExpiresAt),
|
ExpiresAt: jwt.NewNumericDate(refreshExpiresAt),
|
||||||
|
ID: refreshJTI.String(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh, err := util.SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey)
|
refresh, err := util.SignJwtToken(refreshClaims, h.cfg.Jwt.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ApiTokens{
|
return types.NewSignedToken(idToken, idExpiresAt, idJTI), types.NewSignedToken(access, accessExpiresAt, accessJTI), types.NewSignedToken(refresh, refreshExpiresAt, refreshJTI), nil
|
||||||
ID: ApiToken{
|
|
||||||
Token: idToken,
|
|
||||||
Expiration: idExpiresIn.Seconds(),
|
|
||||||
},
|
|
||||||
Access: ApiToken{
|
|
||||||
Token: access,
|
|
||||||
Expiration: accessExpiresIn.Seconds(),
|
|
||||||
},
|
|
||||||
Refresh: ApiToken{
|
|
||||||
Token: refresh,
|
|
||||||
Expiration: refreshExpiresIn.Seconds(),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -174,40 +156,71 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
fmt.Printf("Code received: %s\n", code)
|
fmt.Printf("Code received: %s\n", code)
|
||||||
|
|
||||||
session, err := h.cache.GetAuthCode(r.Context(), code)
|
codeSession, err := h.cache.GetAuthCode(r.Context(), code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERR: Failed to find session under the code %s: %v\n", code, err)
|
log.Printf("ERR: Failed to find session under the code %s: %v\n", code, err)
|
||||||
web.Error(w, "no session found under this auth code", http.StatusNotFound)
|
web.Error(w, "no session found under this auth code", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("DEBUG: Fetched code session: %#v\n", session)
|
log.Printf("DEBUG: Fetched code session: %#v\n", codeSession)
|
||||||
|
|
||||||
apiService, err := h.repo.GetApiServiceCID(r.Context(), session.ClientID)
|
apiService, err := h.repo.GetApiServiceCID(r.Context(), codeSession.ClientID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERR: Could not find API service with client %s: %v\n", session.ClientID, err)
|
log.Printf("ERR: Could not find API service with client %s: %v\n", codeSession.ClientID, err)
|
||||||
web.Error(w, "service is not registered", http.StatusForbidden)
|
web.Error(w, "service is not registered", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.ClientID != clientId {
|
if codeSession.ClientID != clientId {
|
||||||
web.Error(w, "invalid auth", http.StatusUnauthorized)
|
web.Error(w, "invalid auth", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(session.UserID))
|
user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(codeSession.UserID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
web.Error(w, "requested user not found", http.StatusNotFound)
|
web.Error(w, "requested user not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens, err := h.signApiTokens(&user, &apiService, &session.Nonce)
|
id, access, refresh, err := h.signApiTokens(&user, &apiService, &codeSession.Nonce)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("ERR: Failed to sign api tokens:", err)
|
log.Println("ERR: Failed to sign api tokens:", err)
|
||||||
web.Error(w, "failed to sign tokens", http.StatusInternalServerError)
|
web.Error(w, "failed to sign tokens", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("DEBUG: Created api tokens: %v\n\n%v\n\n%v\n", id.ID.String(), access.ID.String(), refresh.ID.String())
|
||||||
|
|
||||||
|
userId, err := uuid.Parse(codeSession.UserID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR: Failed to parse user '%s' uuid: %v\n", codeSession.UserID, err)
|
||||||
|
web.Error(w, "failed to sign tokens", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ipAddr := util.GetClientIP(r)
|
||||||
|
ua := r.UserAgent()
|
||||||
|
|
||||||
|
session, err := h.repo.CreateServiceSession(r.Context(), repository.CreateServiceSessionParams{
|
||||||
|
ServiceID: apiService.ID,
|
||||||
|
ClientID: apiService.ClientID,
|
||||||
|
UserID: &userId,
|
||||||
|
ExpiresAt: &refresh.ExpiresAt,
|
||||||
|
LastActive: nil,
|
||||||
|
IpAddress: &ipAddr,
|
||||||
|
UserAgent: &ua,
|
||||||
|
AccessTokenID: &access.ID,
|
||||||
|
RefreshTokenID: &refresh.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERR: Failed to create new service session: %v\n", err)
|
||||||
|
web.Error(w, "failed to create session", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("INFO: Service session created for '%s' client_id with '%s' id\n", apiService.ClientID, session.ID.String())
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
IdToken string `json:"id_token"`
|
IdToken string `json:"id_token"`
|
||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"token_type"`
|
||||||
@ -219,11 +232,11 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := Response{
|
response := Response{
|
||||||
IdToken: tokens.ID.Token,
|
IdToken: id.Token,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
AccessToken: tokens.Access.Token,
|
AccessToken: access.Token,
|
||||||
RefreshToken: tokens.Refresh.Token,
|
RefreshToken: refresh.Token,
|
||||||
ExpiresIn: tokens.Access.Expiration,
|
ExpiresIn: access.ExpiresAt.Sub(time.Now()).Seconds(),
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +282,7 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens, err := h.signApiTokens(&user, &apiService, nil)
|
id, access, refresh, err := h.signApiTokens(&user, &apiService, nil)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
IdToken string `json:"id_token"`
|
IdToken string `json:"id_token"`
|
||||||
@ -280,11 +293,11 @@ func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := Response{
|
response := Response{
|
||||||
IdToken: tokens.ID.Token,
|
IdToken: id.Token,
|
||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
AccessToken: tokens.Access.Token,
|
AccessToken: access.Token,
|
||||||
RefreshToken: tokens.Refresh.Token,
|
RefreshToken: refresh.Token,
|
||||||
ExpiresIn: tokens.Access.Expiration,
|
ExpiresIn: access.ExpiresAt.Sub(time.Now()).Seconds(),
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("DEBUG: refresh - sending following response: %#v\n", response)
|
log.Printf("DEBUG: refresh - sending following response: %#v\n", response)
|
||||||
|
@ -20,7 +20,7 @@ INSERT INTO service_sessions (
|
|||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, NOW(), $4, $5,
|
$1, $2, $3, NOW(), $4, $5,
|
||||||
$6, $7, $8, $9,
|
$6, $7, $8, $9,
|
||||||
TRUE, $8, $9
|
TRUE, $10, $11
|
||||||
)
|
)
|
||||||
RETURNING id, service_id, client_id, user_id, issued_at, expires_at, last_active, ip_address, user_agent, access_token_id, refresh_token_id, is_active, revoked_at, scope, claims
|
RETURNING id, service_id, client_id, user_id, issued_at, expires_at, last_active, ip_address, user_agent, access_token_id, refresh_token_id, is_active, revoked_at, scope, claims
|
||||||
`
|
`
|
||||||
@ -35,6 +35,8 @@ type CreateServiceSessionParams struct {
|
|||||||
UserAgent *string `json:"user_agent"`
|
UserAgent *string `json:"user_agent"`
|
||||||
AccessTokenID *uuid.UUID `json:"access_token_id"`
|
AccessTokenID *uuid.UUID `json:"access_token_id"`
|
||||||
RefreshTokenID *uuid.UUID `json:"refresh_token_id"`
|
RefreshTokenID *uuid.UUID `json:"refresh_token_id"`
|
||||||
|
Scope *string `json:"scope"`
|
||||||
|
Claims []byte `json:"claims"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateServiceSession(ctx context.Context, arg CreateServiceSessionParams) (ServiceSession, error) {
|
func (q *Queries) CreateServiceSession(ctx context.Context, arg CreateServiceSessionParams) (ServiceSession, error) {
|
||||||
@ -48,6 +50,8 @@ func (q *Queries) CreateServiceSession(ctx context.Context, arg CreateServiceSes
|
|||||||
arg.UserAgent,
|
arg.UserAgent,
|
||||||
arg.AccessTokenID,
|
arg.AccessTokenID,
|
||||||
arg.RefreshTokenID,
|
arg.RefreshTokenID,
|
||||||
|
arg.Scope,
|
||||||
|
arg.Claims,
|
||||||
)
|
)
|
||||||
var i ServiceSession
|
var i ServiceSession
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
|
21
internal/types/token.go
Normal file
21
internal/types/token.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SignedToken struct {
|
||||||
|
Token string
|
||||||
|
ExpiresAt time.Time
|
||||||
|
ID uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSignedToken(token string, expiresAt time.Time, jti uuid.UUID) *SignedToken {
|
||||||
|
return &SignedToken{
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
ID: jti,
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ INSERT INTO service_sessions (
|
|||||||
) VALUES (
|
) VALUES (
|
||||||
$1, $2, $3, NOW(), $4, $5,
|
$1, $2, $3, NOW(), $4, $5,
|
||||||
$6, $7, $8, $9,
|
$6, $7, $8, $9,
|
||||||
TRUE, $8, $9
|
TRUE, $10, $11
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import ApiServiceCredentialsModal from "@/feature/ApiServiceCredentialsModal";
|
|||||||
import { useApiServices } from "@/store/admin/apiServices";
|
import { useApiServices } from "@/store/admin/apiServices";
|
||||||
import { useCallback, type FC } from "react";
|
import { useCallback, type FC } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Link, useNavigate } from "react-router";
|
import { Link } from "react-router";
|
||||||
|
|
||||||
interface FormData {
|
interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
@ -32,12 +32,10 @@ const ApiServiceCreatePage: FC = () => {
|
|||||||
|
|
||||||
const credentials = useApiServices((state) => state.createdCredentials);
|
const credentials = useApiServices((state) => state.createdCredentials);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (data: FormData) => {
|
async (data: FormData) => {
|
||||||
console.log("Form submitted:", data);
|
console.log("Form submitted:", data);
|
||||||
const success = await createApiService({
|
await createApiService({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
description: data.description ?? "",
|
description: data.description ?? "",
|
||||||
redirect_uris: data.redirectUris.trim().split("\n"),
|
redirect_uris: data.redirectUris.trim().split("\n"),
|
||||||
@ -47,11 +45,11 @@ const ApiServiceCreatePage: FC = () => {
|
|||||||
: ["authorization_code"],
|
: ["authorization_code"],
|
||||||
is_active: data.enabled,
|
is_active: data.enabled,
|
||||||
});
|
});
|
||||||
if (success) {
|
// if (success) {
|
||||||
navigate("/admin/api-services");
|
// navigate("/admin/api-services");
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
[createApiService, navigate],
|
[createApiService],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -71,7 +71,7 @@ const AdminServiceSessionsPage: FC = () => {
|
|||||||
Service
|
Service
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-700 dark:text-white/70 border border-l-0 border-gray-300 dark:border-gray-700">
|
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-700 dark:text-white/70 border border-l-0 border-gray-300 dark:border-gray-700">
|
||||||
Source
|
User + IP
|
||||||
</th>
|
</th>
|
||||||
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-700 dark:text-white/70 border border-l-0 border-gray-300 dark:border-gray-700">
|
<th className="px-6 py-3 text-left text-sm font-semibold text-gray-700 dark:text-white/70 border border-l-0 border-gray-300 dark:border-gray-700">
|
||||||
Status
|
Status
|
||||||
@ -109,26 +109,44 @@ const AdminServiceSessionsPage: FC = () => {
|
|||||||
key={session.id}
|
key={session.id}
|
||||||
className="hover:bg-gray-50 dark:hover:bg-gray-800"
|
className="hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||||
>
|
>
|
||||||
<td className="px-5 py-3 text-sm text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-700">
|
|
||||||
<div className="flex flex-row items-center gap-2 justify-start">
|
|
||||||
{typeof session.user?.profile_picture === "string" && (
|
|
||||||
<Avatar
|
|
||||||
avatarId={session.user.profile_picture}
|
|
||||||
className="w-7 h-7 min-w-7"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Link to={`/admin/users/view/${session.user_id}`}>
|
|
||||||
<p className="cursor-pointer text-blue-500 text-nowrap">
|
|
||||||
{session.user?.full_name ?? ""}{" "}
|
|
||||||
{session.user_id === profile?.id ? "(You)" : ""}
|
|
||||||
</p>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-5 py-3 text-sm text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-700">
|
<td className="px-5 py-3 text-sm text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-700">
|
||||||
{/* <SessionSource deviceInfo={session.} /> */}
|
{/* <SessionSource deviceInfo={session.} /> */}
|
||||||
<p>{session.client_id}</p>
|
{typeof session.api_service?.icon_url === "string" && (
|
||||||
|
<Avatar
|
||||||
|
avatarId={session.api_service.icon_url}
|
||||||
|
className="w-7 h-7 min-w-7"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Link to={`/admin/api-services/view/${session.service_id}`}>
|
||||||
|
<p className="cursor-pointer text-blue-500 text-nowrap">
|
||||||
|
{session.api_service?.name ?? session.client_id}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td className="px-5 py-3 text-sm text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-700">
|
||||||
|
<div className="flex flex-col items-stretch gap-2 justify-start">
|
||||||
|
<div className="flex flex-row items-center gap-2 justify-start">
|
||||||
|
{typeof session.user?.profile_picture === "string" && (
|
||||||
|
<Avatar
|
||||||
|
avatarId={session.user.profile_picture}
|
||||||
|
className="w-7 h-7 min-w-7"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex flex-col items-stretch justify-center">
|
||||||
|
<Link to={`/admin/users/view/${session.user_id}`}>
|
||||||
|
<p className="cursor-pointer text-blue-500 text-nowrap">
|
||||||
|
{session.user?.full_name ?? ""}{" "}
|
||||||
|
{session.user_id === profile?.id ? "(You)" : ""}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
<p className="opacity-75">
|
||||||
|
{session.ip_address ?? "No IP available"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-5 py-3 text-sm border border-gray-300 dark:border-gray-700">
|
<td className="px-5 py-3 text-sm border border-gray-300 dark:border-gray-700">
|
||||||
<span
|
<span
|
||||||
|
@ -22,7 +22,7 @@ interface IApiServicesState {
|
|||||||
|
|
||||||
fetch: () => Promise<void>;
|
fetch: () => Promise<void>;
|
||||||
fetchSingle: (id: string) => Promise<void>;
|
fetchSingle: (id: string) => Promise<void>;
|
||||||
create: (req: CreateApiServiceRequest) => Promise<bool>;
|
create: (req: CreateApiServiceRequest) => Promise<boolean>;
|
||||||
resetCredentials: () => void;
|
resetCredentials: () => void;
|
||||||
|
|
||||||
toggling: boolean;
|
toggling: boolean;
|
||||||
|
Reference in New Issue
Block a user