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