Repository: logto-io/logto
File: packages/connectors/connector-oidc/src/types.ts (TypeScript)

Code before:
export const scopePostProcessor = (scope: string) => {
  const splitScopes = scope.split(delimiter).filter(Boolean);

  if (!splitScopes.includes(scopeOpenid)) {
    return [...splitScopes, scopeOpenid].join(' ');
  }

  return scope;
};

// See https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims.
// We only concern a subset of them, and social identity provider usually does not provide a complete set of them.
export const idTokenProfileStandardClaimsGuard = z.object({
  sub: z.string(),
  name: z.string().nullish(),
  email: z.string().nullish(),
  email_verified: z.boolean().nullish(),
  phone: z.string().nullish(),
  phone_verified: z.boolean().nullish(),
  picture: z.string().nullish(),
  profile: z.string().nullish(),
  nonce: z.string().nullish(),
});

// Extend `idTokenProfileStandardClaimsGuard` by accepting string-typed boolean claims.
export const idTokenProfileStandardClaimsGuardAcceptingStringTypedBooleanClaims =
  idTokenProfileStandardClaimsGuard.omit({ email_verified: true, phone_verified: true }).extend({
    email_verified: z
      .boolean()
      .nullish()
      .or(z.string().transform((value: string) => yes(value))),
    phone_verified: z
      .boolean()
      .nullish()
      .or(z.string().transform((value: string) => yes(value))),
  });

export const userProfileGuard = z.object({
  id: z.preprocess(String, z.string()),
  email: z.string().optional(),
  phone: z.string().optional(),
  name: z.string().optional(),
  avatar: z.string().optional(),
});

export type UserProfile = z.infer<typeof userProfileGuard>;

/**
 * We remove `nonce` in `authRequestOptionalConfigGuard` because it should be a randomly generated string,
 * should not be fixed in config and will be generated in Logto core according to `response_type` of authorization request.
 */

Review comment (suggestion):
```suggestion
/**
 * An extension of `idTokenProfileStandardClaimsGuard` that ...
 */
export const idTokenClaimsGuard =
```

Code after:
 * Space-delimited 'scope' MUST contain 'openid', see https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth
 */
export const scopePostProcessor = (scope: string) => {
  const splitScopes = scope.split(delimiter).filter(Boolean);

  if (!splitScopes.includes(scopeOpenid)) {
    return [...splitScopes, scopeOpenid].join(' ');
  }

  return scope;
};

/**
 * See https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims.
 * We only concern a subset of them, and social identity provider usually does not provide a complete set of them.
 */
export const idTokenProfileStandardClaimsGuard = z.object({
  sub: z.string(),
  name: z.string().nullish(),
  email: z.string().nullish(),
  email_verified: z.boolean().nullish(),
  phone: z.string().nullish(),
  phone_verified: z.boolean().nullish(),
  picture: z.string().nullish(),
  profile: z.string().nullish(),
  nonce: z.string().nullish(),
});

/**
 * Extend `idTokenProfileStandardClaimsGuard` by accepting string-typed boolean claims.
 */
export const idTokenClaimsGuardWithStringBooleans = idTokenProfileStandardClaimsGuard
  .omit({ email_verified: true, phone_verified: true })
  .extend({
    email_verified: z
      .boolean()
      .or(z.string().transform((value: string) => yes(value)))
      .nullish(),
    phone_verified: z
      .boolean()
      .or(z.string().transform((value: string) => yes(value)))
      .nullish(),
  });

export const userProfileGuard = z.object({
  id: z.preprocess(String, z.string()),
  email: z.string().optional(),
  phone: z.string().optional(),
  name: z.string().optional(),
  avatar: z.string().optional(),
});