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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kyd-shared-badge",
3
- "version": "0.3.42",
3
+ "version": "0.3.43",
4
4
  "private": false,
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -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 ConnectedPlatforms = ({ accounts }: { accounts?: ConnectedAccount[] }) => {
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;