SDK və alətlər
Node.js üçün @cosuite/connect paketi, uyğunluq testi CLI-ı, imza test vektorları və .NET, PHP, Python üçün yoxlanılmış imza funksiyaları.
Connect v1 müqaviləsini reallaşdırmaq və test etmək üçün lazım olan hər şey. Düzgünlük proqramlaşdırma dilindən asılı deyil: eyni imza test vektorları və eyni uyğunluq testi hər tətbiqə şamil olunur.
@cosuite/connect (Node.js)
TypeScript ilə yazılmış, Node.js 18.17+ üçün müqavilənin istinad reallaşdırması. MIT lisenziyası.
npm install https://cosuite.io/developers/sdk/cosuite-connect-1.8.0.tgz
Paket @cosuite/connect adı ilə quraşdırılır, import sətirləri dəyişmir. Paket npm reyestrində də dərc olunacaq; o vaxta qədər bu ünvandan quraşdırın.
| Eksport | Məqsəd |
|---|---|
signRequest, verifyRequest, computeSignature |
HMAC sorğu imzası |
handleConnectRequest, matchAsistRoute, okResponse, errorResponse |
İmzalı endpoint qabığı: gövdə limiti, imza, request-id təkrarı, xəta zərfi |
ConnectError |
Müqavilə xətası qaytarmaq üçün handler-dən at (new ConnectError("tenant_not_found", "…")) |
CoSuiteClient |
Tətbiqin → CoSuite klienti (getEntitlements, sendEvent) |
createSsoVerifier, isSafeRedirect |
SSO tokeninin yoxlanması (ES256 + JWKS) və təhlükəsiz yönləndirmə yoxlaması |
schemas |
Hər sorğu və cavab üçün zod sxemləri və TypeScript tipləri |
verifyGatewayRequest, gatewayServer |
Müştəri API şlüzünün tətbiqinizə ötürdüyü sorğuların yoxlanması |
checkModuleDefinitions, checkOpenApi |
Uyğunluq testinin işə saldığı modul tərifi yoxlamaları |
MemoryReplayStore |
Request-id və SSO jti təkrarı üçün tək prosesli anbar (bir neçə proses işlədəndə ReplayStore-u Redis və ya verilənlər bazan üzərində reallaşdır) |
createMockAsist, serveMockAsist |
Bütün müqaviləni reallaşdıran, yaddaşda işləyən istinad tətbiqi |
@cosuite/connect/conformance |
Kitabxana kimi uyğunluq testi (runConformance) |
handleConnectRequest veb-standart Request və Response ilə işləyir, ona görə də Next.js route handler-lərində, Hono, Bun və ya Deno-da olduğu kimi, Express və ya Fastify-da isə kiçik bir körpü ilə işləyir. Tam route üçün bax: 10 dəqiqəlik sürətli başlanğıc.
SSO-nun yoxlanması
import { createSsoVerifier, MemoryReplayStore } from "@cosuite/connect";
const verifySso = createSsoVerifier({
issuer: "https://cosuite.io",
audience: "dent", // your app key
jwksUrl: "https://api.cosuite.io/connect/v1/jwks.json",
replay: new MemoryReplayStore(),
});
// POST /api/cosuite/v1/sso, form field "token"
const claims = await verifySso(token); // throws ConnectError when invalid, expired or reused
// claims.sub, claims.tid, claims.email, claims.role, claims.redirect …
Hadisələrin göndərilməsi
import { CoSuiteClient } from "@cosuite/connect";
const cosuite = new CoSuiteClient({
baseUrl: "https://api.cosuite.io/connect/v1",
keyId: process.env.COSUITE_KEY_ID!,
secret: process.env.COSUITE_CONNECT_SECRET!,
});
await cosuite.sendEvent({
id: "evt_01J9ZK3Q", // stable id from your outbox; also used as the request id
type: "usage.threshold_reached",
tenantId: "org_8f2c",
occurredAt: new Date().toISOString(),
data: { metric: "smsPerMonth", percent: 80 },
});
Uyğunluq testi
Uyğunluq testi tətbiqinin URL-inə qoşulur və müqaviləni başdan sona sınaqdan keçirir. Tətbiqinin hansı dildə yazıldığı onun üçün fərq etmir. CoSuite onu tətbiqin canlıya çıxmazdan əvvəl işə salır, sən də onu CI-da staging-ə qarşı işlətməlisən: qırmızıdırsa, buraxma.
export COSUITE_CONNECT_SECRET=… # the secret of the key you test with; never put it on the command line
npx --yes --package=https://cosuite.io/developers/sdk/cosuite-connect-1.8.0.tgz cosuite-connect conformance --url https://staging.dentcloud.example --key csk_dent_test
| Seçim | Mənası |
|---|---|
--url |
Tətbiqinin əsas URL-i (müqavilə /api/cosuite/v1 altında yerləşir) |
--key |
İmzalamaq üçün keyId |
--secret-env |
Gizli açarı saxlayan mühit dəyişəninin adı (standart COSUITE_CONNECT_SECRET) |
--sso-key, --issuer |
SSO yoxlamaları üçün şəxsi JWK faylı və issuer. Onlar olmadan SSO yoxlamaları ötürülür. |
--json |
CI üçün maşın tərəfindən oxunan çıxış |
Bu yoxlamaları ardıcıllıqla işə salır:
- manifest və health;
- modul tərifi (müqavilə 1.7, yalnız doldurduğun sahələr), o cümlədən hər OpenAPI sənədinin yüklənib yoxlanması;
- imza hücumları: imzasız, vaxtı keçmiş zaman möhürü, naməlum açar, dəyişdirilmiş gövdə, dəyişdirilmiş metod;
test: trueilə tenant açılması, request-id təkrarı (eyni gövdə ilk cavabı qaytarır, fərqli gövdə409), idempotent yenidən açılma, naməlum bağlama kodu409 invalid_link_code, naməlum tenant404, etibarsız gövdə400,PATCH;- entitlement versiyaları: 2, sonra 1 (tətbiq olunmamalıdır), yenə eyni 2 (tətbiq olunmamalıdır), sonra naməlum modul və sahə ilə 3 (nəzərə alınmamalıdır), sonra geri oxuma;
- üzvlərin əlavə edilməsi, siyahılanması və çıxarılması;
- müştərilər (
customerselan edirsənsə): səhifə forması, etibarsız limit və cursor; - istifadə və xülasə, o cümlədən xülasə üçün 1 saniyəlik vaxt büdcəsi və ən çoxu 6 kart;
- SSO (
--sso-keyilə): etibarlı token istənilən səhifəyə çatır, təkrar istifadə olunmuş token, başqa tətbiq üçün token, pozulmuş imza; - tenant-ın silinməsi.
Hər şey keçəndə çıxış kodu 0, əks halda 1 olur. İnsan üçün nəzərdə tutulmuş çıxış hazırda yoxlama adlarını türkcə yazır və YEŞİL (yaşıl) və ya KIRMIZI (qırmızı) ilə bitir; nəticəni parse edirsənsə, --json istifadə et.
Lokal istinad tətbiqi
Müqaviləni artıq reallaşdıran, yaddaşda işləyən tətbiqə qarşı inkişaf et və ya onun davranışını öz tətbiqininkilə müqayisə et:
COSUITE_CONNECT_SECRET=… npx --yes --package=https://cosuite.io/developers/sdk/cosuite-connect-1.8.0.tgz cosuite-connect mock --asist dent --port 4010 --key csk_dent_dev [--single-tenant-users]
--single-tenant-users onu e-poçtun yalnız bir hesaba aid ola bildiyi tətbiq kimi davranmağa məcbur edir (409 member_conflict).
İmza test vektorları
Hər reallaşdırma məhz bu imzaları yaratmalıdır. Bütün sətirlər UTF-8-dir; zaman möhürü yazıldığı kimi (onluq sətir kimi) istifadə olunur.
| Hal | Gizli açar | Metod | pathAndQuery | Zaman möhürü | Gövdə | İmza |
|---|---|---|---|---|---|---|
| GET, gövdəsiz | s3cr3t-key-0123456789abcdef0123456789 |
GET |
/api/cosuite/v1/manifest |
1761469200 |
(boş) | v1=0742611504ab28bc645cd09b102fe78ba0ec192698d3905fcc0dbb7519c1bce9 |
| PUT, JSON gövdə (kiçik hərfli metod) | s3cr3t-key-0123456789abcdef0123456789 |
put |
/api/cosuite/v1/tenants/org_8f2c/entitlements |
1761469200 |
{"version":42,"status":"active"} |
v1=ae6344f124a267db473e8c4e3da23413e35926426959bebfe447ec287570fe6f |
| Sorğu sətri və qeyri-ASCII mətn | başka-gizli-ə |
POST |
/app/api/cosuite/v1/tenants?x=1&y=%C3%A7 |
1761469201 |
{"name":"Yılmaz Otel — Bakı"} |
v1=c14f7a789416cde9b3b1efe524695b267ee8b12d0d2a91fdce5926dc73bb5601 |
| Yolda kodlaşdırılmış id | k |
DELETE |
/api/cosuite/v1/tenants/org%3Aa%2Fb/members/usr_1 |
0 |
(boş) | v1=e83120960821a9035bdd4077ccf127dc077e4c8058bbf4b9633ae46071308744 |
Birinci halın kanonik sətri belədir:
GET|/api/cosuite/v1/manifest|1761469200|e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Eyni vektorlar @cosuite/connect paketində test-vectors/signature.json kimi gəlir.
İmza nümunələri
Digər dillər üçün ayrıca paket yoxdur: imza tək bir funksiyadır. Aşağıdakı hər nümunə yuxarıdakı dörd test vektorunun hamısına qarşı işə salınıb və onları dəqiq yaradır. Ətrafındakı qaydaları unutma: xam gövdə baytlarını oxu, JSON-u parse etməzdən əvvəl yoxla, ən çoxu 256 KB qəbul et, yazma sorğularının id-lərini 10 dəqiqə saxla və xəta zərfini qaytar.
.NET (C#)
using System.Security.Cryptography;
using System.Text;
public static class CoSuiteSignature
{
public const int MaxSkewSeconds = 300;
public static string Compute(string secret, string method, string pathAndQuery, string timestamp, byte[] body)
{
var bodyHash = Convert.ToHexStringLower(SHA256.HashData(body));
var canonical = $"{method.ToUpperInvariant()}|{pathAndQuery}|{timestamp}|{bodyHash}";
var mac = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(canonical));
return "v1=" + Convert.ToHexStringLower(mac);
}
/// <summary>secrets: the valid secrets for this keyId (two during key rotation).</summary>
public static bool Verify(IReadOnlyList<string> secrets, string method, string pathAndQuery, string timestamp, byte[] body, string signature, long nowUnix)
{
if (!long.TryParse(timestamp, out var ts) || Math.Abs(nowUnix - ts) > MaxSkewSeconds) return false;
if (!signature.StartsWith("v1=") || signature.Length != 67) return false;
var given = Encoding.ASCII.GetBytes(signature);
var match = false;
foreach (var s in secrets)
match |= CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(Compute(s, method, pathAndQuery, timestamp, body)), given);
return match;
}
}
ASP.NET Core-da Request.EnableBuffering() çağır və xam gövdəni model binding-dən əvvəl oxu; pathAndQuery Request.PathBase + Request.Path + Request.QueryString olur. Müqavilə endpoint-lərindən ProblemDetails qaytarma; müqavilənin xəta zərfindən istifadə et. Convert.ToHexStringLower .NET 9 tələb edir; köhnə versiyalarda Convert.ToHexString(...).ToLowerInvariant() istifadə et.
Microsoft.IdentityModel.JsonWebTokens ilə SSO:
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
var jwks = new JsonWebKeySet(await http.GetStringAsync("https://api.cosuite.io/connect/v1/jwks.json")); // cache for 1 hour
var result = await new JsonWebTokenHandler().ValidateTokenAsync(token, new TokenValidationParameters
{
ValidIssuer = "https://cosuite.io",
ValidAudience = "dent", // your app key
IssuerSigningKeys = jwks.GetSigningKeys(),
ValidAlgorithms = new[] { SecurityAlgorithms.EcdsaSha256 },
ClockSkew = TimeSpan.FromSeconds(30),
RequireExpirationTime = true,
});
// Then: reject if exp - iat > 60, keep jti for 2 minutes (single use), allow only "/path" redirects.
PHP
PHP 8.0+ (yalnız daxili hash genişlənməsindən istifadə edir):
<?php
const COSUITE_MAX_SKEW_SECONDS = 300;
function cosuite_signature(string $secret, string $method, string $pathAndQuery, string $timestamp, string $body): string
{
$canonical = strtoupper($method) . '|' . $pathAndQuery . '|' . $timestamp . '|' . hash('sha256', $body);
return 'v1=' . hash_hmac('sha256', $canonical, $secret);
}
/** @param string[] $secrets the valid secrets for this keyId (two during key rotation) */
function cosuite_verify(array $secrets, string $method, string $pathAndQuery, string $timestamp, string $body, string $signature, ?int $now = null): bool
{
$now ??= time();
if (!ctype_digit($timestamp) || abs($now - (int) $timestamp) > COSUITE_MAX_SKEW_SECONDS) {
return false; // 401 signature_expired
}
$match = false;
foreach ($secrets as $secret) {
$match = hash_equals(cosuite_signature($secret, $method, $pathAndQuery, $timestamp, $body), $signature) || $match;
}
return $match; // false -> 401 invalid_signature
}
// Usage: $body = file_get_contents('php://input'); $path = $_SERVER['REQUEST_URI'];
// cosuite_verify($secrets, $_SERVER['REQUEST_METHOD'], $path, $_SERVER['HTTP_X_COSUITE_TIMESTAMP'], $body, $_SERVER['HTTP_X_COSUITE_SIGNATURE']);
Python
Python 3.10+ (yalnız standart kitabxana):
import hashlib
import hmac
import time
MAX_SKEW_SECONDS = 300
def cosuite_signature(secret: str, method: str, path_and_query: str, timestamp: str, body: bytes) -> str:
body_hash = hashlib.sha256(body).hexdigest()
canonical = f"{method.upper()}|{path_and_query}|{timestamp}|{body_hash}"
mac = hmac.new(secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256).hexdigest()
return "v1=" + mac
def cosuite_verify(secrets: list[str], method: str, path_and_query: str, timestamp: str,
body: bytes, signature: str, now: int | None = None) -> bool:
now = int(time.time()) if now is None else now
if not timestamp.isdigit() or abs(now - int(timestamp)) > MAX_SKEW_SECONDS:
return False # 401 signature_expired
match = False
for secret in secrets: # two secrets are valid during key rotation
expected = cosuite_signature(secret, method, path_and_query, timestamp, body)
match |= hmac.compare_digest(expected, signature)
return match # False -> 401 invalid_signature
Xam gövdə baytlarını (Django-da request.body, Flask-da request.get_data()) və klientin göndərdiyi xam sorğu hədəfini dəqiq olduğu kimi ötür (məsələn, WSGI serverindən RAW_URI və ya REQUEST_URI). Yolu yenidən quran framework köməkçiləri %3A kimi simvolları dekodlaşdıra və ya yenidən kodlaşdıra, imzanı poza bilər.
PHP və ya Python-da SSO üçün ES256 və JWKS dəstəkləyən, aktiv saxlanılan istənilən JWT kitabxanasından istifadə et (məsələn, firebase/php-jwt və ya PyJWKClient ilə PyJWT) və SSO yoxlamalarını tətbiq et.