@@ -6,6 +6,7 @@ package code
6
6
import (
7
7
"context"
8
8
"encoding/json"
9
+ "io"
9
10
"net/http"
10
11
"sort"
11
12
"strings"
@@ -16,6 +17,7 @@ import (
16
17
"github.com/tidwall/gjson"
17
18
18
19
"github.com/ory/herodot"
20
+ "github.com/ory/jsonschema/v3"
19
21
"github.com/ory/kratos/continuity"
20
22
"github.com/ory/kratos/courier"
21
23
"github.com/ory/kratos/driver/config"
@@ -247,6 +249,128 @@ func (s *Strategy) PopulateMethod(r *http.Request, f flow.Flow) error {
247
249
return nil
248
250
}
249
251
252
+ func (s * Strategy ) GetSupportedVerificationChannels (ctx context.Context ) (map [identity.VerifiableAddressType ]bool , error ) {
253
+ channels := make (map [identity.VerifiableAddressType ]bool )
254
+
255
+ schemaList , err := s .deps .IdentityTraitsSchemas (ctx )
256
+ if err != nil {
257
+ return nil , err
258
+ }
259
+
260
+ schemaID := s .deps .Config ().DefaultIdentityTraitsSchemaID (ctx )
261
+ identitySchema , err := schemaList .GetByID (schemaID )
262
+ if err != nil {
263
+ return nil , err
264
+ }
265
+
266
+ rawURL := identitySchema .RawURL
267
+ if rawURL == "" && identitySchema .URL != nil {
268
+ rawURL = identitySchema .URL .String ()
269
+ }
270
+
271
+ schemaFile , err := jsonschema .LoadURL (ctx , rawURL )
272
+ if err != nil {
273
+ return nil , err
274
+ }
275
+
276
+ schemaData , err := io .ReadAll (io .LimitReader (schemaFile , 1024 * 1024 ))
277
+ if err != nil {
278
+ return nil , err
279
+ }
280
+
281
+ var schemaJSON map [string ]interface {}
282
+ if err := json .Unmarshal (schemaData , & schemaJSON ); err != nil {
283
+ return nil , err
284
+ }
285
+
286
+ // properties.traits.properties.<trait>."ory.sh/kratos".verification.via
287
+ if props , ok := schemaJSON ["properties" ].(map [string ]interface {}); ok {
288
+ if traits , ok := props ["traits" ].(map [string ]interface {}); ok {
289
+ if traitProps , ok := traits ["properties" ].(map [string ]interface {}); ok {
290
+ for _ , propValue := range traitProps {
291
+ if prop , ok := propValue .(map [string ]interface {}); ok {
292
+ if ext , ok := prop [schema .ExtensionName ].(map [string ]interface {}); ok {
293
+ if verification , ok := ext ["verification" ].(map [string ]interface {}); ok {
294
+ // Set the appropriate channel based on verification via value
295
+ switch verification ["via" ].(string ) {
296
+ case identity .ChannelTypeEmail :
297
+ channels [identity .VerifiableAddressTypeEmail ] = true
298
+ case identity .ChannelTypeSMS :
299
+ channels [identity .VerifiableAddressTypePhone ] = true
300
+ }
301
+ }
302
+ }
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }
308
+
309
+ return channels , nil
310
+ }
311
+
312
+ func (s * Strategy ) GetVerificationRequiredField (ctx context.Context ) (pointer string , property string , err error ) {
313
+ channels , err := s .GetSupportedVerificationChannels (ctx )
314
+ if err != nil {
315
+ return "#/identifier" , "identifier" , err
316
+ }
317
+
318
+ activeChannelCount := 0
319
+ for _ , active := range channels {
320
+ if active {
321
+ activeChannelCount ++
322
+ }
323
+ }
324
+
325
+ // Return field-specific errors if only one channel is supported
326
+ if activeChannelCount == 1 {
327
+ if channels [identity .VerifiableAddressTypeEmail ] {
328
+ return "#/email" , "email" , nil
329
+ }
330
+ if channels [identity .VerifiableAddressTypePhone ] {
331
+ return "#/phone" , "phone" , nil
332
+ }
333
+ }
334
+
335
+ // Default to generic identifier error
336
+ return "#/identifier" , "identifier" , nil
337
+ }
338
+
339
+ func (s * Strategy ) addVerificationNodes (ctx context.Context , nodes * node.Nodes , email interface {}, phone interface {}) error {
340
+ channels , err := s .GetSupportedVerificationChannels (ctx )
341
+ if err != nil {
342
+ return err
343
+ }
344
+
345
+ activeChannelCount := 0
346
+ for _ , active := range channels {
347
+ if active {
348
+ activeChannelCount ++
349
+ }
350
+ }
351
+
352
+ opts := []node.InputAttributesModifier {}
353
+ if activeChannelCount == 1 {
354
+ opts = append (opts , node .WithRequiredInputAttribute )
355
+ }
356
+
357
+ if channels [identity .VerifiableAddressTypeEmail ] {
358
+ nodes .Append (
359
+ node .NewInputField ("email" , email , node .CodeGroup , node .InputAttributeTypeEmail , opts ... ).
360
+ WithMetaLabel (text .NewInfoNodeInputEmail ()),
361
+ )
362
+ }
363
+
364
+ if channels [identity .VerifiableAddressTypePhone ] {
365
+ nodes .Append (
366
+ node .NewInputField ("phone" , phone , node .CodeGroup , node .InputAttributeTypeTel , opts ... ).
367
+ WithMetaLabel (text .NewInfoNodeInputPhone ()),
368
+ )
369
+ }
370
+
371
+ return nil
372
+ }
373
+
250
374
func (s * Strategy ) populateChooseMethodFlow (r * http.Request , f flow.Flow ) error {
251
375
ctx := r .Context ()
252
376
switch f := f .(type ) {
@@ -260,15 +384,10 @@ func (s *Strategy) populateChooseMethodFlow(r *http.Request, f flow.Flow) error
260
384
WithMetaLabel (text .NewInfoNodeLabelContinue ()),
261
385
)
262
386
case * verification.Flow :
263
- // Add both email and phone fields for verification
264
- f .GetUI ().Nodes .Append (
265
- node .NewInputField ("email" , nil , node .CodeGroup , node .InputAttributeTypeEmail ).
266
- WithMetaLabel (text .NewInfoNodeInputEmail ()),
267
- )
268
- f .GetUI ().Nodes .Append (
269
- node .NewInputField ("phone" , nil , node .CodeGroup , node .InputAttributeTypeTel ).
270
- WithMetaLabel (text .NewInfoNodeInputPhone ()),
271
- )
387
+ if err := s .addVerificationNodes (ctx , & f .GetUI ().Nodes , nil , nil ); err != nil {
388
+ return err
389
+ }
390
+
272
391
f .GetUI ().Nodes .Append (
273
392
node .NewInputField ("method" , s .ID (), node .CodeGroup , node .InputAttributeTypeSubmit ).
274
393
WithMetaLabel (text .NewInfoNodeLabelContinue ()),
0 commit comments