kyd-shared-badge 0.3.42 → 0.3.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -136,6 +136,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
136
136
|
return { label: em.label, description: em.description, roleName: role.name };
|
|
137
137
|
})()}
|
|
138
138
|
countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
|
|
139
|
+
accountAuthenticity={assessmentResult?.account_authenticity}
|
|
139
140
|
/>
|
|
140
141
|
</Reveal>
|
|
141
142
|
{/* Coaching / Evidence under header when present */}
|
|
@@ -285,7 +286,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
285
286
|
|
|
286
287
|
{/* Connected Platforms */}
|
|
287
288
|
<Reveal headless={isHeadless}>
|
|
288
|
-
<ConnectedPlatforms accounts={connected} />
|
|
289
|
+
<ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
|
|
289
290
|
</Reveal>
|
|
290
291
|
|
|
291
292
|
|
|
@@ -202,6 +202,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
202
202
|
return { label: em.label, description: em.description, roleName: role.name };
|
|
203
203
|
})()}
|
|
204
204
|
countries={(assessmentResult?.screening_sources?.ip_risk_analysis?.raw_data?.countries) || []}
|
|
205
|
+
accountAuthenticity={assessmentResult?.account_authenticity}
|
|
205
206
|
/>
|
|
206
207
|
</Reveal>
|
|
207
208
|
<div className={'rounded-xl shadow-xl p-6 sm:p-8 mt-6 border'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
|
|
@@ -428,7 +429,7 @@ const SharedBadgeDisplay = ({ badgeData, chatProps, headless }: { badgeData: Pub
|
|
|
428
429
|
</div>
|
|
429
430
|
)}
|
|
430
431
|
<div>
|
|
431
|
-
<ConnectedPlatforms accounts={connected} />
|
|
432
|
+
<ConnectedPlatforms accounts={connected} authenticity={assessmentResult?.account_authenticity} />
|
|
432
433
|
</div>
|
|
433
434
|
</div>
|
|
434
435
|
</div>
|
|
@@ -25,11 +25,63 @@ const ProviderIcon = ({ name }: { name?: string }) => {
|
|
|
25
25
|
return <span className="inline-block w-4 h-4 rounded-full" style={{ backgroundColor: 'var(--icon-button-secondary)' }} />;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
const
|
|
28
|
+
const formatProviderName = (name?: string) => {
|
|
29
|
+
const n = (name || '').trim();
|
|
30
|
+
if (!n) return '';
|
|
31
|
+
const lower = n.toLowerCase();
|
|
32
|
+
if (lower === 'so' || lower === 'stack_overflow') return 'Stack Overflow';
|
|
33
|
+
return n.charAt(0).toUpperCase() + n.slice(1);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const ConnectedPlatforms = ({ accounts, authenticity }: { accounts?: ConnectedAccount[]; authenticity?: { label?: string; description?: string; oauth_connected?: string[]; mismatches?: Array<{ provider?: string; detail?: string }> } }) => {
|
|
29
37
|
const list = Array.isArray(accounts) ? accounts : [];
|
|
30
38
|
if (list.length === 0) return null;
|
|
31
39
|
return (
|
|
32
40
|
<div className="pt-8">
|
|
41
|
+
{(() => {
|
|
42
|
+
const label = (authenticity?.label || '').toLowerCase();
|
|
43
|
+
const isCritical = label === 'likely inauthentic';
|
|
44
|
+
const isSuspicious = label === 'suspicious';
|
|
45
|
+
if (!label) return null;
|
|
46
|
+
return (
|
|
47
|
+
<div className={"mb-4 rounded-md border p-3"} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
|
|
48
|
+
<div className="flex items-start gap-2">
|
|
49
|
+
<span className="text-sm font-semibold" style={{ color: isCritical ? '#B00020' : isSuspicious ? '#9f580a' : 'var(--text-main)' }}>
|
|
50
|
+
{(authenticity?.label) || ''}
|
|
51
|
+
</span>
|
|
52
|
+
</div>
|
|
53
|
+
{authenticity?.description ? (
|
|
54
|
+
<div className={'text-xs mt-1'} style={{ color: 'var(--text-secondary)' }}>{authenticity.description}</div>
|
|
55
|
+
) : null}
|
|
56
|
+
{Array.isArray(authenticity?.oauth_connected) && authenticity!.oauth_connected!.length > 0 ? (
|
|
57
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
58
|
+
{authenticity!.oauth_connected!.map((p, i) => (
|
|
59
|
+
<span key={`${p}-${i}`} className="inline-flex items-center gap-1 px-2 py-1 rounded text-xs font-medium border" style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)', color: 'var(--text-secondary)' }}>
|
|
60
|
+
<span className="text-sm" style={{ color: 'var(--text-main)' }}><ProviderIcon name={p} /></span>
|
|
61
|
+
<span style={{ color: 'var(--text-main)' }}>{formatProviderName(p)}</span>
|
|
62
|
+
</span>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
) : null}
|
|
66
|
+
{Array.isArray(authenticity?.mismatches) && authenticity!.mismatches!.length > 0 ? (
|
|
67
|
+
<div className="mt-3 rounded-md border p-3" style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
|
|
68
|
+
<div className="text-sm font-semibold" style={{ color: 'var(--text-main)' }}>Observed Identity Mismatches</div>
|
|
69
|
+
<ul className="mt-2 space-y-2">
|
|
70
|
+
{authenticity!.mismatches!.map((m, idx) => (
|
|
71
|
+
<li key={idx} className="flex items-start gap-2 text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
72
|
+
<span className="text-sm" style={{ color: 'var(--text-main)' }}><ProviderIcon name={m.provider} /></span>
|
|
73
|
+
<span>
|
|
74
|
+
<span className="font-medium" style={{ color: 'var(--text-main)' }}>{formatProviderName(m.provider)}</span>
|
|
75
|
+
{m.detail ? `: ${m.detail}` : ''}
|
|
76
|
+
</span>
|
|
77
|
+
</li>
|
|
78
|
+
))}
|
|
79
|
+
</ul>
|
|
80
|
+
</div>
|
|
81
|
+
) : null}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
})()}
|
|
33
85
|
<h4 className={'text-lg font-semibold mb-3'} style={{ color: 'var(--text-main)' }}>Sources (Connected or Linked)</h4>
|
|
34
86
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
35
87
|
{list.map((acct, idx) => (
|
|
@@ -4,7 +4,7 @@ import Image from 'next/image';
|
|
|
4
4
|
import { formatLocalDate } from '../utils/date';
|
|
5
5
|
import countriesLib from 'i18n-iso-countries';
|
|
6
6
|
import enLocale from 'i18n-iso-countries/langs/en.json';
|
|
7
|
-
import { FiInfo } from 'react-icons/fi';
|
|
7
|
+
import { FiInfo, FiAlertTriangle } from 'react-icons/fi';
|
|
8
8
|
|
|
9
9
|
// Register English locale once at module import time
|
|
10
10
|
countriesLib.registerLocale(enLocale);
|
|
@@ -39,9 +39,10 @@ interface ReportHeaderProps {
|
|
|
39
39
|
summary?: string;
|
|
40
40
|
enterpriseMatch?: { label?: string; description?: string; roleName?: string } | null;
|
|
41
41
|
countries?: string[];
|
|
42
|
+
accountAuthenticity?: { label?: string; description?: string };
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [] }: ReportHeaderProps) => {
|
|
45
|
+
const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImageUrl, summary, enterpriseMatch, countries = [], accountAuthenticity }: ReportHeaderProps) => {
|
|
45
46
|
// Use the dynamic image if available, otherwise fall back to the score-based one.
|
|
46
47
|
const finalBadgeImageUrl = badgeImageUrl || getBadgeImageUrl(score || 0);
|
|
47
48
|
const tint = hexToRgba(pickTint(score || 0), 0.06);
|
|
@@ -58,6 +59,22 @@ const ReportHeader = ({ badgeId, developerName, updatedAt, score = 0, badgeImage
|
|
|
58
59
|
className={'mb-8 p-6 rounded-xl shadow-lg border'}
|
|
59
60
|
style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)', backgroundImage: `linear-gradient(${tint}, ${tint})` }}
|
|
60
61
|
>
|
|
62
|
+
{(() => {
|
|
63
|
+
const label = (accountAuthenticity?.label || '').toLowerCase();
|
|
64
|
+
if (!label) return null;
|
|
65
|
+
const isCritical = label === 'likely inauthentic';
|
|
66
|
+
const isSuspicious = label === 'suspicious';
|
|
67
|
+
if (!isCritical && !isSuspicious) return null;
|
|
68
|
+
return (
|
|
69
|
+
<div className={'mb-4 rounded-md border p-3 flex items-start gap-2'} style={{ backgroundColor: 'var(--content-card-background)', borderColor: 'var(--icon-button-secondary)' }}>
|
|
70
|
+
<span className={'mt-0.5'} style={{ color: isCritical ? '#B00020' : '#9f580a' }}><FiAlertTriangle size={18} /></span>
|
|
71
|
+
<div className={'text-sm'}>
|
|
72
|
+
<div className={'font-semibold'} style={{ color: 'var(--text-main)' }}>Account Authenticity {isCritical ? 'Warning' : 'Notice'}</div>
|
|
73
|
+
<div className={'mt-1'} style={{ color: 'var(--text-secondary)' }}>{accountAuthenticity?.description || (isCritical ? 'We detected signals that some linked accounts may be inauthentic. Review sources below.' : 'Some inconsistencies were observed across linked accounts. Review sources below.')}</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
})()}
|
|
61
78
|
<div className="flex flex-col md:flex-row items-center md:items-stretch gap-6">
|
|
62
79
|
{/* Left Half: Badge Image with robust centered overlay */}
|
|
63
80
|
<div className="w-full md:w-1/3 flex items-center justify-center self-stretch">
|
package/src/types.ts
CHANGED
|
@@ -268,6 +268,14 @@ export interface AssessmentResult {
|
|
|
268
268
|
graph_insights?: GraphInsightsPayload;
|
|
269
269
|
enterprise_match?: EnterpriseMatch;
|
|
270
270
|
enterprise_use_cases?: EnterpriseUseCases;
|
|
271
|
+
// New: account authenticity assessment
|
|
272
|
+
account_authenticity?: {
|
|
273
|
+
label: 'Normal' | 'Suspicious' | 'Likely Inauthentic';
|
|
274
|
+
description?: string;
|
|
275
|
+
// Optional diagnostics for UI/tooling
|
|
276
|
+
oauth_connected?: string[];
|
|
277
|
+
mismatches?: Array<{ provider?: string; detail?: string }>;
|
|
278
|
+
};
|
|
271
279
|
}
|
|
272
280
|
export interface ClientMetadata {
|
|
273
281
|
ipAddress: string;
|