Add signature handlers
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -5,12 +5,13 @@ import { buildEndpoint } from "../utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
|
||||
type Document = {
|
||||
id: string;
|
||||
policy_version_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
signed: boolean;
|
||||
signed?: boolean;
|
||||
};
|
||||
|
||||
type SigningResponse = {
|
||||
@@ -38,7 +39,7 @@ export default function SigningRequestsPage() {
|
||||
|
||||
async function fetchDocuments() {
|
||||
try {
|
||||
const response = await fetch(buildEndpoint("/api/signing-requests"), {
|
||||
const response = await fetch(buildEndpoint("/api/console/v1/policies/signing-requests"), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
@@ -50,8 +51,18 @@ export default function SigningRequestsPage() {
|
||||
throw new Error("Failed to fetch signing documents");
|
||||
}
|
||||
|
||||
const data: SigningResponse = await response.json();
|
||||
setSigningData(data);
|
||||
const documents: Document[] = await response.json();
|
||||
// Transform the API response to match our internal structure
|
||||
const enhancedDocuments = documents.map(doc => ({
|
||||
...doc,
|
||||
signed: false
|
||||
}));
|
||||
|
||||
setSigningData({
|
||||
documents: enhancedDocuments,
|
||||
requesterName: "Requester", // Default values as they're not in the API response
|
||||
requesterOrganization: "Organization"
|
||||
});
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "An unknown error occurred");
|
||||
} finally {
|
||||
@@ -69,7 +80,7 @@ export default function SigningRequestsPage() {
|
||||
const docToSign = signingData.documents[currentDocIndex];
|
||||
|
||||
try {
|
||||
const response = await fetch(buildEndpoint(`/api/signing-requests/${docToSign.id}/sign`), {
|
||||
const response = await fetch(buildEndpoint(`/api/console/v1/policies/signing-requests/${docToSign.policy_version_id}/sign`), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${token}`,
|
||||
@@ -203,7 +214,9 @@ export default function SigningRequestsPage() {
|
||||
|
||||
<CardContent>
|
||||
<div className="border rounded-md p-4 min-h-[400px] bg-muted/20">
|
||||
<div dangerouslySetInnerHTML={{ __html: currentDoc.content }} />
|
||||
<div className="prose prose-olive max-w-none">
|
||||
<ReactMarkdown>{currentDoc.content}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
@@ -174,6 +175,51 @@ func (s *PolicyService) Create(
|
||||
return policy, policyVersion, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) ListSigningRequests(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
peopleID gid.GID,
|
||||
) ([]map[string]any, error) {
|
||||
q := `
|
||||
SELECT
|
||||
p.title,
|
||||
pv.content,
|
||||
pv.id AS policy_version_id
|
||||
FROM
|
||||
policies p
|
||||
INNER JOIN policy_versions pv ON pv.policy_id = p.id
|
||||
INNER JOIN policy_version_signatures pvs ON pvs.policy_version_id = pv.id
|
||||
WHERE
|
||||
p.tenant_id = $1
|
||||
AND pvs.signed_by = $2
|
||||
AND pvs.signed_at IS NULL
|
||||
`
|
||||
|
||||
var results []map[string]any
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
rows, err := conn.Query(ctx, q, s.svc.scope.GetTenantID(), peopleID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query policies: %w", err)
|
||||
}
|
||||
|
||||
results, err = pgx.CollectRows(rows, pgx.RowToMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (s *PolicyService) SendSigningNotifications(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
|
||||
@@ -90,7 +90,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
corsOpts := cors.Options{
|
||||
AllowedOrigins: s.cfg.AllowedOrigins,
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "HEAD"},
|
||||
AllowedHeaders: []string{"content-type", "traceparent"},
|
||||
AllowedHeaders: []string{"content-type", "traceparent", "authorization"},
|
||||
ExposedHeaders: []string{"x-Request-id"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 600, // 10 minutes (chrome >= 76 maximum value c.f. https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/cors/preflight_result.cc;drc=52002151773d8cd9ffc5f557cd7cc880fddcae3e;l=36)
|
||||
|
||||
@@ -18,9 +18,11 @@ package console_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
@@ -35,6 +37,7 @@ import (
|
||||
"github.com/getprobo/probo/pkg/saferedirect"
|
||||
"github.com/getprobo/probo/pkg/securecookie"
|
||||
"github.com/getprobo/probo/pkg/server/api/console/v1/schema"
|
||||
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||
"github.com/getprobo/probo/pkg/usrmgr"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
@@ -77,6 +80,68 @@ func UserFromContext(ctx context.Context) *coredata.User {
|
||||
func NewMux(proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg AuthConfig, connectorRegistry *connector.ConnectorRegistry, safeRedirect *saferedirect.SafeRedirect) *chi.Mux {
|
||||
r := chi.NewMux()
|
||||
|
||||
r.Get(
|
||||
"/policies/signing-requests",
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
if token == "" {
|
||||
http.Error(w, "token is required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
data, err := statelesstoken.ValidateToken[probo.SigningRequestData](authCfg.CookieSecret, probo.TokenTypeSigningRequest, token)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
svc := proboSvc.WithTenant(data.Data.OrganizationID.TenantID())
|
||||
|
||||
requests, err := svc.Policies.ListSigningRequests(r.Context(), data.Data.OrganizationID, data.Data.PeopleID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(requests)
|
||||
},
|
||||
)
|
||||
|
||||
r.Post(
|
||||
"/policies/signing-requests/{policy_version_id}/sign",
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("Authorization")
|
||||
if token == "" {
|
||||
http.Error(w, "token is required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
data, err := statelesstoken.ValidateToken[probo.SigningRequestData](authCfg.CookieSecret, probo.TokenTypeSigningRequest, token)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
policyVersionID, err := gid.ParseGID(chi.URLParam(r, "policy_version_id"))
|
||||
if err != nil {
|
||||
http.Error(w, "invalid policy version id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
svc := proboSvc.WithTenant(data.Data.OrganizationID.TenantID())
|
||||
|
||||
if err := svc.Policies.SignPolicyVersion(r.Context(), policyVersionID, data.Data.PeopleID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
)
|
||||
|
||||
r.Post("/auth/register", SignUpHandler(usrmgrSvc, authCfg))
|
||||
r.Post("/auth/login", SignInHandler(usrmgrSvc, authCfg))
|
||||
r.Delete("/auth/logout", SignOutHandler(usrmgrSvc, authCfg))
|
||||
|
||||
Reference in New Issue
Block a user