snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { gen } from "../../../helpers"
|
||||
import * as httpbin from "../../../helpers/httpbin"
|
||||
import * as oauth2 from "../../../helpers/oauth2"
|
||||
|
||||
context("OpenID Provider", () => {
|
||||
const client = {
|
||||
auth_endpoint: "http://localhost:4744/oauth2/auth",
|
||||
token_endpoint: "http://localhost:4744/oauth2/token",
|
||||
id: Cypress.env("OIDC_DUMMY_CLIENT_ID"),
|
||||
secret: Cypress.env("OIDC_DUMMY_CLIENT_SECRET"),
|
||||
token_endpoint_auth_method: "client_secret_basic",
|
||||
grant_types: ["authorization_code", "refresh_token"],
|
||||
response_types: ["code", "id_token"],
|
||||
scopes: ["openid", "offline", "email", "website"],
|
||||
callbacks: [
|
||||
"http://localhost:5555/callback",
|
||||
"https://ory-network-httpbin-ijakee5waq-ez.a.run.app/anything",
|
||||
],
|
||||
}
|
||||
|
||||
before(() => {
|
||||
cy.deleteMail()
|
||||
cy.useConfigProfile("oidc-provider")
|
||||
cy.proxy("express")
|
||||
})
|
||||
|
||||
it("login", () => {
|
||||
const email = gen.email()
|
||||
const password = gen.password()
|
||||
cy.registerApi({
|
||||
email: email,
|
||||
password: password,
|
||||
fields: { "traits.website": "http://t1.local" },
|
||||
})
|
||||
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
|
||||
cy.visit(url)
|
||||
|
||||
// kratos login ui
|
||||
cy.get("[name=identifier]").type(email)
|
||||
cy.get("[name=password]").type(password)
|
||||
cy.get("[type='submit'][value='password']").click()
|
||||
|
||||
// consent ui
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
|
||||
const scope = ["offline", "openid"]
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(escape(window.atob(token.id_token.split(".")[1]))),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(["password"])
|
||||
})
|
||||
})
|
||||
|
||||
it("login-without-scopes", () => {
|
||||
const email = gen.email()
|
||||
const password = gen.password()
|
||||
cy.registerApi({
|
||||
email: email,
|
||||
password: password,
|
||||
fields: { "traits.website": "http://t1.local" },
|
||||
})
|
||||
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
cy.visit(url)
|
||||
|
||||
// kratos login ui
|
||||
cy.get("[name=identifier]").type(email)
|
||||
cy.get("[name=password]").type(password)
|
||||
cy.get("[type='submit'][value='password']").click()
|
||||
|
||||
// consent ui
|
||||
cy.get("#accept").click()
|
||||
|
||||
const scope = ["offline", "openid"]
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).not.to.have.property("id_token")
|
||||
expect(token).not.to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("")
|
||||
})
|
||||
})
|
||||
|
||||
it("respects-login-remember-config", () => {
|
||||
let odicLogin = () => {
|
||||
const email = gen.email()
|
||||
const password = gen.password()
|
||||
cy.registerApi({
|
||||
email: email,
|
||||
password: password,
|
||||
fields: { "traits.website": "http://t1.local" },
|
||||
})
|
||||
|
||||
let url = oauth2.getDefaultAuthorizeURL(client)
|
||||
cy.visit(url)
|
||||
|
||||
// kratos login ui
|
||||
cy.get("[name=identifier]").type(email)
|
||||
cy.get("[name=password]").type(password)
|
||||
cy.get("[type='submit'][value='password']").click()
|
||||
// consent ui
|
||||
cy.get("#accept").click()
|
||||
}
|
||||
|
||||
cy.clearAllCookies()
|
||||
cy.updateConfigFile((config) => {
|
||||
config.session.cookie = config.session.cookie || {}
|
||||
config.session.cookie.persistent = true
|
||||
config.session.lifespan = "1234s"
|
||||
return config
|
||||
})
|
||||
|
||||
odicLogin()
|
||||
cy.getCookies({ domain: "localhost" })
|
||||
cy.getCookie("ory_hydra_session_dev", { domain: "localhost" }).should(
|
||||
"not.be.null",
|
||||
)
|
||||
cy.getCookie("ory_hydra_session_dev", { domain: "localhost" }).then(
|
||||
(cookie: Cypress.Cookie) => {
|
||||
let expected = Date.now() / 1000 + 1234
|
||||
let precision = 10
|
||||
expect(cookie.expiry).to.be.lessThan(expected + precision)
|
||||
expect(cookie.expiry).to.be.greaterThan(expected - precision)
|
||||
},
|
||||
)
|
||||
|
||||
cy.clearAllCookies()
|
||||
cy.updateConfigFile((config) => {
|
||||
config.session.cookie = config.session.cookie || {}
|
||||
config.session.cookie.persistent = false
|
||||
return config
|
||||
})
|
||||
|
||||
odicLogin()
|
||||
cy.getCookie("ory_hydra_session_dev", { domain: "localhost" }).should(
|
||||
"be.null",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
context("OpenID Provider - change between flows", () => {
|
||||
const client = {
|
||||
auth_endpoint: "http://localhost:4744/oauth2/auth",
|
||||
token_endpoint: "http://localhost:4744/oauth2/token",
|
||||
id: Cypress.env("OIDC_DUMMY_CLIENT_ID"),
|
||||
secret: Cypress.env("OIDC_DUMMY_CLIENT_SECRET"),
|
||||
token_endpoint_auth_method: "client_secret_basic",
|
||||
grant_types: ["authorization_code", "refresh_token"],
|
||||
response_types: ["code", "id_token"],
|
||||
scopes: ["openid", "offline", "email", "website"],
|
||||
callbacks: [
|
||||
"http://localhost:5555/callback",
|
||||
"https://ory-network-httpbin-ijakee5waq-ez.a.run.app/anything",
|
||||
],
|
||||
}
|
||||
|
||||
function doConsent(amrs?: string[]) {
|
||||
cy.url().should("contain", "/consent")
|
||||
|
||||
// consent ui
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
|
||||
const scope = ["offline", "openid"]
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(escape(window.atob(token.id_token.split(".")[1]))),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(amrs || ["password"])
|
||||
})
|
||||
}
|
||||
|
||||
before(() => {
|
||||
cy.useConfigProfile("oidc-provider")
|
||||
cy.updateConfigFile((config) => {
|
||||
config.selfservice.allowed_return_urls = [
|
||||
oauth2.getDefaultAuthorizeURL(client),
|
||||
]
|
||||
config.oauth2_provider.override_return_to = true
|
||||
return config
|
||||
})
|
||||
cy.proxy("express")
|
||||
})
|
||||
|
||||
it("switch to registration flow", () => {
|
||||
const identity = gen.identityWithWebsite()
|
||||
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
|
||||
cy.visit(url)
|
||||
cy.get("[href*='/registration']").click()
|
||||
cy.url().should("contain", "/registration")
|
||||
|
||||
cy.get("[name='traits.email']").type(identity.email)
|
||||
cy.get("[name='password']").type(identity.password)
|
||||
cy.get("[name='traits.website']").type(identity.fields["traits.website"])
|
||||
cy.get("[type='submit'][value='password']").click()
|
||||
|
||||
doConsent()
|
||||
})
|
||||
|
||||
it("switch to recovery flow with password reset", () => {
|
||||
cy.updateConfigFile((config) => {
|
||||
config.selfservice.flows.recovery.lifespan = "1m"
|
||||
config.selfservice.methods.link.config.lifespan = "1m"
|
||||
config.selfservice.flows.verification.enabled = false
|
||||
if (!config.selfservice.flows.recovery) {
|
||||
config.selfservice.flows.recovery = {}
|
||||
}
|
||||
config.selfservice.flows.recovery.enabled = true
|
||||
config.selfservice.flows.settings.privileged_session_max_age = "5m"
|
||||
return config
|
||||
})
|
||||
cy.deleteMail()
|
||||
cy.useRecoveryStrategy("code")
|
||||
cy.notifyUnknownRecipients("recovery", false)
|
||||
|
||||
const identity = gen.identityWithWebsite()
|
||||
cy.registerApi(identity)
|
||||
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
cy.visit(url)
|
||||
cy.get("[href*='/recovery']").click()
|
||||
|
||||
cy.get("input[name='email']").type(identity.email)
|
||||
cy.get("button[value='code']").click()
|
||||
cy.get('[data-testid="ui/message/1060003"]').should(
|
||||
"have.text",
|
||||
"An email containing a recovery code has been sent to the email address you provided. If you have not received an email, check the spelling of the address and make sure to use the address you registered with.",
|
||||
)
|
||||
|
||||
cy.recoveryEmailWithCode({ expect: { email: identity.email } })
|
||||
cy.get("button[value='code']").click()
|
||||
|
||||
cy.get('[data-testid="ui/message/1060001"]', { timeout: 30000 }).should(
|
||||
"contain.text",
|
||||
"You successfully recovered your account. ",
|
||||
)
|
||||
|
||||
cy.getSession()
|
||||
cy.location("pathname").should("eq", "/settings")
|
||||
// do a password change
|
||||
const newPassword = gen.password()
|
||||
cy.get('input[name="password"]').clear().type(newPassword)
|
||||
cy.get('button[value="password"]').click()
|
||||
|
||||
// since we aren't skipping the consent, Kratos will ask us to login again
|
||||
cy.get('input[name="password"]').clear().type(newPassword)
|
||||
cy.get('button[value="password"]').click()
|
||||
|
||||
// we should now end up on the consent screen
|
||||
doConsent(["code_recovery", "password"])
|
||||
})
|
||||
|
||||
it("switch to recovery flow with oidc link", () => {
|
||||
cy.deleteMail()
|
||||
cy.longRecoveryLifespan()
|
||||
cy.longLinkLifespan()
|
||||
cy.enableRecovery()
|
||||
cy.useRecoveryStrategy("code")
|
||||
cy.notifyUnknownRecipients("recovery", false)
|
||||
cy.longPrivilegedSessionTime()
|
||||
|
||||
const fakeOidcFlow = (
|
||||
identity: ReturnType<typeof gen.identityWithWebsite>,
|
||||
) => {
|
||||
cy.get("input[name='username']").type(identity.email)
|
||||
cy.get("button[name='action'][value='accept']").click()
|
||||
// consent screen for the 'fake' oidc provider
|
||||
cy.url().should("contain", "/consent")
|
||||
// consent ui for 3rd party integration to Kratos (sign in with Google)
|
||||
// it will look the same as Hydra consent ui since we are reusing it as
|
||||
// a 'fake' oidc provider
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
}
|
||||
|
||||
const identity = gen.identityWithWebsite()
|
||||
cy.registerApi(identity)
|
||||
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
cy.visit(url)
|
||||
|
||||
cy.get("[href*='/recovery']").click()
|
||||
cy.get("input[name='email']").type(identity.email)
|
||||
cy.get("button[value='code']").click()
|
||||
|
||||
cy.get('[data-testid="ui/message/1060003"]').should(
|
||||
"have.text",
|
||||
"An email containing a recovery code has been sent to the email address you provided. If you have not received an email, check the spelling of the address and make sure to use the address you registered with.",
|
||||
)
|
||||
|
||||
cy.recoveryEmailWithCode({ expect: { email: identity.email } })
|
||||
cy.get("button[value='code']").click()
|
||||
|
||||
cy.get('[data-testid="ui/message/1060001"]', { timeout: 30000 }).should(
|
||||
"contain.text",
|
||||
"You successfully recovered your account. ",
|
||||
)
|
||||
|
||||
cy.getSession()
|
||||
cy.location("pathname").should("eq", "/settings")
|
||||
|
||||
// (OAauth2+Kratos provider '/settings' -> Fake OAuth2 '/login')
|
||||
|
||||
// we are reusing Hydra as an OIDC provider for Kratos and doing an OIDC account
|
||||
// linking process here
|
||||
// this is to check if the user can recover their account through OIDC linking
|
||||
// and still be redirected back to the original request
|
||||
cy.get("button[value='google']").click()
|
||||
cy.location("pathname").should("eq", "/login")
|
||||
fakeOidcFlow(identity)
|
||||
|
||||
// it should return us back to the initial OAauth2 login flow
|
||||
// sign in through the 'fake' oidc provider
|
||||
cy.get("button[type='submit'][value='google']").click()
|
||||
|
||||
// login again at the 'fake' oidc provider
|
||||
fakeOidcFlow(identity)
|
||||
|
||||
// complete the consent screen
|
||||
doConsent(["oidc"])
|
||||
})
|
||||
})
|
||||
166
test/e2e/cypress/integration/profiles/oidc-provider/mfa.spec.ts
Normal file
166
test/e2e/cypress/integration/profiles/oidc-provider/mfa.spec.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { gen } from "../../../helpers"
|
||||
import { routes as express } from "../../../helpers/express"
|
||||
import * as oauth2 from "../../../helpers/oauth2"
|
||||
import * as httpbin from "../../../helpers/httpbin"
|
||||
import { TOTP } from "otpauth"
|
||||
|
||||
context("OIDC Provider 2FA", () => {
|
||||
const client = {
|
||||
auth_endpoint: "http://localhost:4744/oauth2/auth",
|
||||
token_endpoint: "http://localhost:4744/oauth2/token",
|
||||
id: Cypress.env("OIDC_DUMMY_CLIENT_ID"),
|
||||
secret: Cypress.env("OIDC_DUMMY_CLIENT_SECRET"),
|
||||
token_endpoint_auth_method: "client_secret_basic",
|
||||
grant_types: ["authorization_code", "refresh_token"],
|
||||
response_types: ["code", "id_token"],
|
||||
scopes: ["openid", "offline", "email", "website"],
|
||||
callbacks: [
|
||||
"http://localhost:5555/callback",
|
||||
"https://ory-network-httpbin-ijakee5waq-ez.a.run.app/anything",
|
||||
],
|
||||
}
|
||||
|
||||
;[
|
||||
{
|
||||
login: express.login,
|
||||
settings: express.settings,
|
||||
base: express.base,
|
||||
profile: "oidc-provider-mfa",
|
||||
app: "express" as "express",
|
||||
},
|
||||
].forEach(({ settings, login, profile, app, base }) => {
|
||||
describe(`for app ${app}`, () => {
|
||||
let email = gen.email()
|
||||
let password = gen.password()
|
||||
let secret
|
||||
|
||||
before(() => {
|
||||
cy.useConfigProfile(profile)
|
||||
cy.proxy(app)
|
||||
|
||||
email = gen.email()
|
||||
password = gen.password()
|
||||
|
||||
cy.register({
|
||||
email,
|
||||
password,
|
||||
fields: { "traits.website": "http://t1.local" },
|
||||
})
|
||||
cy.visit(settings)
|
||||
|
||||
cy.get('[data-testid="node/text/totp_secret_key/text"]').then(($e) => {
|
||||
secret = $e.text().trim()
|
||||
})
|
||||
cy.get('input[name="totp_code"]').then(($e) => {
|
||||
cy.wrap($e).type(
|
||||
new TOTP({
|
||||
secret,
|
||||
}).generate(),
|
||||
)
|
||||
})
|
||||
cy.get('*[name="method"][value="totp"]').click()
|
||||
cy.expectSettingsSaved()
|
||||
cy.getSession({
|
||||
expectAal: "aal2",
|
||||
expectMethods: ["password", "totp"],
|
||||
})
|
||||
|
||||
cy.clearAllCookies()
|
||||
})
|
||||
|
||||
it("should be be asked to sign in with 2fa if set up", () => {
|
||||
let url = oauth2.getDefaultAuthorizeURL(client)
|
||||
|
||||
cy.get("body")
|
||||
.then((body$) => {
|
||||
// Credits https://github.com/suchipi, https://github.com/cypress-io/cypress/issues/944#issuecomment-444312914
|
||||
const appWindow = body$[0].ownerDocument.defaultView
|
||||
const appIframe = appWindow.parent.document.querySelector("iframe")
|
||||
|
||||
return new Promise((resolve) => {
|
||||
appIframe.onload = () => resolve(undefined)
|
||||
appWindow.location.href = url
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
// kratos login ui
|
||||
cy.get("[name=identifier]").type(email)
|
||||
cy.get("[name=password]").type(password)
|
||||
cy.get("[type=submit]").click()
|
||||
|
||||
cy.get('input[name="totp_code"]').then(($e) => {
|
||||
cy.wrap($e).type(
|
||||
new TOTP({
|
||||
secret,
|
||||
}).generate(),
|
||||
)
|
||||
})
|
||||
cy.get('*[name="method"][value="totp"]').click()
|
||||
|
||||
// consent ui
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
|
||||
let scope = ["offline", "openid"]
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(
|
||||
escape(window.atob(token.id_token.split(".")[1])),
|
||||
),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(["password", "totp"])
|
||||
})
|
||||
|
||||
// We shouldn't need to authenticate again
|
||||
url = oauth2.getDefaultAuthorizeURL(client)
|
||||
|
||||
cy.get("body")
|
||||
.then((body$) => {
|
||||
// Credits https://github.com/suchipi, https://github.com/cypress-io/cypress/issues/944#issuecomment-444312914
|
||||
const appWindow = body$[0].ownerDocument.defaultView
|
||||
const appIframe =
|
||||
appWindow.parent.document.querySelector("iframe")
|
||||
|
||||
return new Promise((resolve) => {
|
||||
appIframe.onload = () => resolve(undefined)
|
||||
appWindow.location.href = url
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
// We get the consent screen instead of login
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(
|
||||
escape(window.atob(token.id_token.split(".")[1])),
|
||||
),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(["password", "totp"])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { APP_URL, gen } from "../../../helpers"
|
||||
import * as oauth2 from "../../../helpers/oauth2"
|
||||
import * as httpbin from "../../../helpers/httpbin"
|
||||
|
||||
context("OpenID Provider", () => {
|
||||
before(() => {
|
||||
cy.useConfigProfile("oidc-provider")
|
||||
cy.proxy("express")
|
||||
})
|
||||
const client = {
|
||||
auth_endpoint: "http://localhost:4744/oauth2/auth",
|
||||
token_endpoint: "http://localhost:4744/oauth2/token",
|
||||
id: Cypress.env("OIDC_DUMMY_CLIENT_ID"),
|
||||
secret: Cypress.env("OIDC_DUMMY_CLIENT_SECRET"),
|
||||
token_endpoint_auth_method: "client_secret_basic",
|
||||
grant_types: ["authorization_code", "refresh_token"],
|
||||
response_types: ["code", "id_token"],
|
||||
scopes: ["openid", "offline", "email", "website"],
|
||||
callbacks: [
|
||||
"http://localhost:5555/callback",
|
||||
"https://ory-network-httpbin-ijakee5waq-ez.a.run.app/anything",
|
||||
],
|
||||
}
|
||||
|
||||
it("registration", () => {
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
|
||||
cy.visit(url)
|
||||
cy.get("[data-testid=signup-link]").click()
|
||||
|
||||
const email = gen.email()
|
||||
const password = gen.password()
|
||||
|
||||
cy.get('[name="traits.email"]').type(email)
|
||||
cy.get("[name=password]").type(password)
|
||||
cy.get('[name="traits.website"]').type("http://example.com")
|
||||
cy.get('input[type=checkbox][name="traits.tos"]').click({ force: true })
|
||||
cy.get('[name="traits.age"]').type("199")
|
||||
cy.get('input[type=checkbox][name="traits.consent"]').click({ force: true })
|
||||
cy.get('input[type=checkbox][name="traits.newsletter"]').click({
|
||||
force: true,
|
||||
})
|
||||
|
||||
cy.get("[type='submit'][value='password']").click()
|
||||
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
|
||||
const scope = ["offline", "openid"]
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(escape(window.atob(token.id_token.split(".")[1]))),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(["password"])
|
||||
})
|
||||
})
|
||||
|
||||
it("registration with session, skip=false and skip=true", () => {
|
||||
const email = gen.email()
|
||||
const password = gen.password()
|
||||
|
||||
cy.register({
|
||||
email,
|
||||
password,
|
||||
fields: {
|
||||
"traits.website": "https://www.ory.sh",
|
||||
"traits.tos": "1",
|
||||
"traits.age": 22,
|
||||
},
|
||||
})
|
||||
|
||||
const url = oauth2.getDefaultAuthorizeURL(client)
|
||||
|
||||
cy.request(url).then((res) => {
|
||||
const lastResp = res.allRequestResponses[1]["Request URL"]
|
||||
const login_challenge = new URL(lastResp).searchParams.get(
|
||||
"login_challenge",
|
||||
)
|
||||
expect(login_challenge).to.not.be.null
|
||||
cy.visit(
|
||||
APP_URL +
|
||||
"/self-service/registration/browser?login_challenge=" +
|
||||
login_challenge,
|
||||
)
|
||||
})
|
||||
|
||||
cy.url().should("contain", "/login")
|
||||
cy.get("[data-testid='login-flow']").should("exist")
|
||||
cy.get("[data-testid='login-flow'] [name='password']").type(password)
|
||||
cy.get(
|
||||
"[data-testid='login-flow'] button[name='method'][value='password']",
|
||||
).click()
|
||||
|
||||
// we want to skip the consent flow here
|
||||
// so we ask to remember the user
|
||||
cy.get("[name='remember']").click()
|
||||
cy.get("#openid").click()
|
||||
cy.get("#offline").click()
|
||||
cy.get("#accept").click()
|
||||
|
||||
const scope = ["offline", "openid"]
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(escape(window.atob(token.id_token.split(".")[1]))),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(["password", "password"])
|
||||
})
|
||||
|
||||
// use the hydra origin to make a new OAuth request from it
|
||||
cy.get("body")
|
||||
.then((body$) => {
|
||||
// Credits https://github.com/suchipi, https://github.com/cypress-io/cypress/issues/944#issuecomment-444312914
|
||||
const appWindow = body$[0].ownerDocument.defaultView
|
||||
const appIframe = appWindow.parent.document.querySelector("iframe")
|
||||
|
||||
return new Promise((resolve) => {
|
||||
appIframe.onload = () => resolve(undefined)
|
||||
appWindow.location.href = "http://localhost:4744/health/ready"
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
// we don't want to redirect here since we only want the login challenge from hydra
|
||||
// we reusing the challenge to navigate to the registration page
|
||||
cy.request({
|
||||
url: oauth2.getDefaultAuthorizeURL(client),
|
||||
followRedirect: false,
|
||||
})
|
||||
.then((res) => {
|
||||
expect(res.redirectedToUrl).to.include("login_challenge")
|
||||
return new URL(res.redirectedToUrl).searchParams.get(
|
||||
"login_challenge",
|
||||
)
|
||||
})
|
||||
.then((login_challenge) => {
|
||||
cy.get("body").then((body$) => {
|
||||
const appWindow = body$[0].ownerDocument.defaultView
|
||||
const appIframe =
|
||||
appWindow.parent.document.querySelector("iframe")
|
||||
|
||||
return new Promise((resolve) => {
|
||||
appIframe.onload = () => resolve(undefined)
|
||||
appWindow.location.href =
|
||||
APP_URL +
|
||||
"/self-service/registration/browser?login_challenge=" +
|
||||
login_challenge
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
httpbin.checkToken(client, scope, (token: any) => {
|
||||
expect(token).to.have.property("access_token")
|
||||
expect(token).to.have.property("id_token")
|
||||
expect(token).to.have.property("refresh_token")
|
||||
expect(token).to.have.property("token_type")
|
||||
expect(token).to.have.property("expires_in")
|
||||
expect(token.scope).to.equal("offline openid")
|
||||
let idToken = JSON.parse(
|
||||
decodeURIComponent(escape(window.atob(token.id_token.split(".")[1]))),
|
||||
)
|
||||
expect(idToken).to.have.property("amr")
|
||||
expect(idToken.amr).to.deep.equal(["password", "password"])
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user