Add EDUT ID alias routes for membership endpoints
Some checks are pending
check / secretapi (push) Waiting to run
Some checks are pending
check / secretapi (push) Waiting to run
This commit is contained in:
parent
a18f9dd19b
commit
b9ca98e13f
@ -1,6 +1,6 @@
|
|||||||
# Secret API Backend (`secretapi`)
|
# Secret API Backend (`secretapi`)
|
||||||
|
|
||||||
Deterministic backend for wallet-first designation, membership activation, and governance install authorization.
|
Deterministic backend for wallet-first designation, EDUT ID activation, and governance install authorization.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
@ -34,6 +34,9 @@ Copy `.env.example` in this folder and set contract/runtime values before deploy
|
|||||||
- `POST /secret/membership/quote`
|
- `POST /secret/membership/quote`
|
||||||
- `POST /secret/membership/confirm`
|
- `POST /secret/membership/confirm`
|
||||||
- `GET /secret/membership/status`
|
- `GET /secret/membership/status`
|
||||||
|
- `POST /secret/id/quote` (alias to membership quote)
|
||||||
|
- `POST /secret/id/confirm` (alias to membership confirm)
|
||||||
|
- `GET /secret/id/status` (alias to membership status)
|
||||||
|
|
||||||
### Marketplace
|
### Marketplace
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,9 @@ func (a *app) routes() http.Handler {
|
|||||||
mux.HandleFunc("/secret/membership/quote", a.withCORS(a.handleMembershipQuote))
|
mux.HandleFunc("/secret/membership/quote", a.withCORS(a.handleMembershipQuote))
|
||||||
mux.HandleFunc("/secret/membership/confirm", a.withCORS(a.handleMembershipConfirm))
|
mux.HandleFunc("/secret/membership/confirm", a.withCORS(a.handleMembershipConfirm))
|
||||||
mux.HandleFunc("/secret/membership/status", a.withCORS(a.handleMembershipStatus))
|
mux.HandleFunc("/secret/membership/status", a.withCORS(a.handleMembershipStatus))
|
||||||
|
mux.HandleFunc("/secret/id/quote", a.withCORS(a.handleMembershipQuote))
|
||||||
|
mux.HandleFunc("/secret/id/confirm", a.withCORS(a.handleMembershipConfirm))
|
||||||
|
mux.HandleFunc("/secret/id/status", a.withCORS(a.handleMembershipStatus))
|
||||||
mux.HandleFunc("/marketplace/offers", a.withCORS(a.handleMarketplaceOffers))
|
mux.HandleFunc("/marketplace/offers", a.withCORS(a.handleMarketplaceOffers))
|
||||||
mux.HandleFunc("/marketplace/offers/", a.withCORS(a.handleMarketplaceOfferByID))
|
mux.HandleFunc("/marketplace/offers/", a.withCORS(a.handleMarketplaceOfferByID))
|
||||||
mux.HandleFunc("/marketplace/checkout/quote", a.withCORS(a.handleMarketplaceCheckoutQuote))
|
mux.HandleFunc("/marketplace/checkout/quote", a.withCORS(a.handleMarketplaceCheckoutQuote))
|
||||||
|
|||||||
@ -168,6 +168,67 @@ func TestMembershipCompanySponsorWithoutOwnerProof(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEDUTIDAliasRoutesActivateMembership(t *testing.T) {
|
||||||
|
a, cfg, cleanup := newTestApp(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ownerKey := mustKey(t)
|
||||||
|
ownerAddr := strings.ToLower(crypto.PubkeyToAddress(ownerKey.PublicKey).Hex())
|
||||||
|
|
||||||
|
intentRes := postJSONExpect[tWalletIntentResponse](t, a, "/secret/wallet/intent", walletIntentRequest{
|
||||||
|
Address: ownerAddr,
|
||||||
|
Origin: "https://edut.ai",
|
||||||
|
Locale: "en",
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusOK)
|
||||||
|
issuedAt, err := time.Parse(time.RFC3339Nano, intentRes.IssuedAt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse issued_at: %v", err)
|
||||||
|
}
|
||||||
|
td := buildTypedData(cfg, designationRecord{
|
||||||
|
Code: intentRes.DesignationCode,
|
||||||
|
DisplayToken: intentRes.DisplayToken,
|
||||||
|
Nonce: intentRes.Nonce,
|
||||||
|
IssuedAt: issuedAt,
|
||||||
|
Origin: "https://edut.ai",
|
||||||
|
})
|
||||||
|
sig := signTypedData(t, ownerKey, td)
|
||||||
|
verifyRes := postJSONExpect[tWalletVerifyResponse](t, a, "/secret/wallet/verify", walletVerifyRequest{
|
||||||
|
IntentID: intentRes.IntentID,
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
Signature: sig,
|
||||||
|
}, http.StatusOK)
|
||||||
|
|
||||||
|
quote := postJSONExpect[membershipQuoteResponse](t, a, "/secret/id/quote", membershipQuoteRequest{
|
||||||
|
DesignationCode: verifyRes.DesignationCode,
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusOK)
|
||||||
|
if strings.TrimSpace(quote.QuoteID) == "" {
|
||||||
|
t.Fatalf("expected quote id from /secret/id/quote, got %+v", quote)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm := postJSONExpect[membershipConfirmResponse](t, a, "/secret/id/confirm", membershipConfirmRequest{
|
||||||
|
DesignationCode: verifyRes.DesignationCode,
|
||||||
|
QuoteID: quote.QuoteID,
|
||||||
|
TxHash: "0x" + strings.Repeat("b", 64),
|
||||||
|
Address: ownerAddr,
|
||||||
|
ChainID: cfg.ChainID,
|
||||||
|
}, http.StatusOK)
|
||||||
|
if confirm.Status != "membership_active" {
|
||||||
|
t.Fatalf("expected membership_active status from /secret/id/confirm, got %+v", confirm)
|
||||||
|
}
|
||||||
|
|
||||||
|
status := getJSONExpect[membershipStatusResponse](t, a, "/secret/id/status?wallet="+ownerAddr, http.StatusOK)
|
||||||
|
if status.Status != "active" {
|
||||||
|
t.Fatalf("expected active status from /secret/id/status, got %+v", status)
|
||||||
|
}
|
||||||
|
if status.DesignationCode != verifyRes.DesignationCode {
|
||||||
|
t.Fatalf("designation mismatch: got=%s want=%s", status.DesignationCode, verifyRes.DesignationCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMembershipConfirmRequiresChainRPCWhenStrictVerificationEnabled(t *testing.T) {
|
func TestMembershipConfirmRequiresChainRPCWhenStrictVerificationEnabled(t *testing.T) {
|
||||||
a, cfg, cleanup := newTestApp(t)
|
a, cfg, cleanup := newTestApp(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|||||||
@ -101,6 +101,7 @@ paths:
|
|||||||
/secret/membership/quote:
|
/secret/membership/quote:
|
||||||
post:
|
post:
|
||||||
summary: Get current membership mint quote
|
summary: Get current membership mint quote
|
||||||
|
description: Canonical technical route. Public EDUT ID alias is `/secret/id/quote`.
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@ -117,6 +118,7 @@ paths:
|
|||||||
/secret/membership/confirm:
|
/secret/membership/confirm:
|
||||||
post:
|
post:
|
||||||
summary: Confirm membership mint transaction
|
summary: Confirm membership mint transaction
|
||||||
|
description: Canonical technical route. Public EDUT ID alias is `/secret/id/confirm`.
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
@ -133,6 +135,7 @@ paths:
|
|||||||
/secret/membership/status:
|
/secret/membership/status:
|
||||||
get:
|
get:
|
||||||
summary: Resolve membership status by wallet or designation code
|
summary: Resolve membership status by wallet or designation code
|
||||||
|
description: Canonical technical route. Public EDUT ID alias is `/secret/id/status`.
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: wallet
|
name: wallet
|
||||||
@ -152,6 +155,63 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/MembershipStatusResponse'
|
$ref: '#/components/schemas/MembershipStatusResponse'
|
||||||
|
/secret/id/quote:
|
||||||
|
post:
|
||||||
|
summary: Get current EDUT ID activation quote
|
||||||
|
description: Alias of `/secret/membership/quote`.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MembershipQuoteRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Quote created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MembershipQuoteResponse'
|
||||||
|
/secret/id/confirm:
|
||||||
|
post:
|
||||||
|
summary: Confirm EDUT ID activation transaction
|
||||||
|
description: Alias of `/secret/membership/confirm`.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MembershipConfirmRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: EDUT ID active
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MembershipConfirmResponse'
|
||||||
|
/secret/id/status:
|
||||||
|
get:
|
||||||
|
summary: Resolve EDUT ID status by wallet or designation code
|
||||||
|
description: Alias of `/secret/membership/status`.
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: wallet
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
pattern: '^0x[a-fA-F0-9]{40}$'
|
||||||
|
- in: query
|
||||||
|
name: designation_code
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: EDUT ID status resolved
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MembershipStatusResponse'
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
WalletSessionHeader:
|
WalletSessionHeader:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user