@@ -2,14 +2,18 @@ import {
22 isSeamHttpOptionsWithApiKey ,
33 isSeamHttpOptionsWithClientSessionToken ,
44 SeamHttpInvalidOptionsError ,
5- type SeamHttpOptions ,
65 type SeamHttpOptionsWithApiKey ,
76 type SeamHttpOptionsWithClientSessionToken ,
87} from './options.js'
8+ import type { Options } from './parse-options.js'
99
1010type Headers = Record < string , string >
1111
12- export const getAuthHeaders = ( options : SeamHttpOptions ) : Headers => {
12+ export const getAuthHeaders = ( options : Options ) : Headers => {
13+ if ( 'publishableKey' in options ) {
14+ return getAuthHeadersForPublishableKey ( options . publishableKey )
15+ }
16+
1317 if ( isSeamHttpOptionsWithApiKey ( options ) ) {
1418 return getAuthHeadersForApiKey ( options )
1519 }
@@ -19,7 +23,7 @@ export const getAuthHeaders = (options: SeamHttpOptions): Headers => {
1923 }
2024
2125 throw new SeamHttpInvalidOptionsError (
22- 'Must specify an apiKey or clientSessionToken ' ,
26+ 'Must specify an apiKey, clientSessionToken, or publishableKey ' ,
2327 )
2428}
2529
@@ -42,6 +46,12 @@ const getAuthHeadersForApiKey = ({
4246 )
4347 }
4448
49+ if ( isPublishableKey ( apiKey ) ) {
50+ throw new SeamHttpInvalidTokenError (
51+ 'A Publishable Key cannot be used as an apiKey' ,
52+ )
53+ }
54+
4555 if ( ! isSeamToken ( apiKey ) ) {
4656 throw new SeamHttpInvalidTokenError (
4757 `Unknown or invalid apiKey format, expected token to start with ${ tokenPrefix } ` ,
@@ -68,6 +78,12 @@ const getAuthHeadersForClientSessionToken = ({
6878 )
6979 }
7080
81+ if ( isPublishableKey ( clientSessionToken ) ) {
82+ throw new SeamHttpInvalidTokenError (
83+ 'A Publishable Key cannot be used as a clientSessionToken' ,
84+ )
85+ }
86+
7187 if ( ! isClientSessionToken ( clientSessionToken ) ) {
7288 throw new SeamHttpInvalidTokenError (
7389 `Unknown or invalid clientSessionToken format, expected token to start with ${ clientSessionTokenPrefix } ` ,
@@ -80,6 +96,36 @@ const getAuthHeadersForClientSessionToken = ({
8096 }
8197}
8298
99+ const getAuthHeadersForPublishableKey = ( publishableKey : string ) : Headers => {
100+ if ( isJwt ( publishableKey ) ) {
101+ throw new SeamHttpInvalidTokenError (
102+ 'A JWT cannot be used as a publishableKey' ,
103+ )
104+ }
105+
106+ if ( isAccessToken ( publishableKey ) ) {
107+ throw new SeamHttpInvalidTokenError (
108+ 'An Access Token cannot be used as a publishableKey' ,
109+ )
110+ }
111+
112+ if ( isClientSessionToken ( publishableKey ) ) {
113+ throw new SeamHttpInvalidTokenError (
114+ 'A Client Session Token Key cannot be used as a publishableKey' ,
115+ )
116+ }
117+
118+ if ( ! isPublishableKey ( publishableKey ) ) {
119+ throw new SeamHttpInvalidTokenError (
120+ `Unknown or invalid publishableKey format, expected token to start with ${ publishableKeyTokenPrefix } ` ,
121+ )
122+ }
123+
124+ return {
125+ 'seam-publishable-key' : publishableKey ,
126+ }
127+ }
128+
83129export class SeamHttpInvalidTokenError extends Error {
84130 constructor ( message : string ) {
85131 super ( `SeamHttp received an invalid token: ${ message } ` )
@@ -88,10 +134,29 @@ export class SeamHttpInvalidTokenError extends Error {
88134 }
89135}
90136
137+ export const warnOnInsecureuserIdentifierKey = (
138+ userIdentifierKey : string ,
139+ ) : void => {
140+ if ( isEmail ( userIdentifierKey ) ) {
141+ // eslint-disable-next-line no-console
142+ console . warn (
143+ ...[
144+ 'Using an email for the userIdentifierKey is insecure and may return an error in the future!' ,
145+ 'This is insecure because an email is common knowledge or easily guessed.' ,
146+ 'Use something with sufficient entropy known only to the owner of the client session.' ,
147+ 'For help choosing a user identifier key see' ,
148+ 'https://docs.seam.co/latest/seam-components/overview/get-started-with-client-side-components#3-select-a-user-identifier-key' ,
149+ ] ,
150+ )
151+ }
152+ }
153+
91154const tokenPrefix = 'seam_'
92155
93156const clientSessionTokenPrefix = 'seam_cst'
94157
158+ const publishableKeyTokenPrefix = 'seam_pk'
159+
95160const isClientSessionToken = ( token : string ) : boolean =>
96161 token . startsWith ( clientSessionTokenPrefix )
97162
@@ -100,3 +165,10 @@ const isAccessToken = (token: string): boolean => token.startsWith('seam_at')
100165const isJwt = ( token : string ) : boolean => token . startsWith ( 'ey' )
101166
102167const isSeamToken = ( token : string ) : boolean => token . startsWith ( tokenPrefix )
168+
169+ const isPublishableKey = ( token : string ) : boolean =>
170+ token . startsWith ( publishableKeyTokenPrefix )
171+
172+ // SOURCE: https://stackoverflow.com/a/46181
173+ const isEmail = ( value : string ) : boolean =>
174+ / ^ [ ^ \s @ ] + @ [ ^ \s @ ] + \. [ ^ \s @ ] + $ / . test ( value )
0 commit comments