diff --git a/docs/docs/.static/api.json b/docs/docs/.static/api.json index fd227715186f..03c62ad4427d 100755 --- a/docs/docs/.static/api.json +++ b/docs/docs/.static/api.json @@ -433,6 +433,36 @@ "title": "NullTime implements sql.NullTime functionality.", "type": "string" }, + "pagination": { + "properties": { + "page": { + "default": 0, + "description": "Pagination Page", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "per_page": { + "default": 250, + "description": "Items per Page\n\nThis is the number of items per page.", + "format": "int64", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "revokedSessions": { + "properties": { + "count": { + "description": "The number of sessions that were revoked.", + "format": "int64", + "type": "integer" + } + }, + "type": "object" + }, "selfServiceBrowserLocationChangeRequiredError": { "properties": { "code": { @@ -925,6 +955,12 @@ }, "type": "object" }, + "sessionList": { + "items": { + "$ref": "#/components/schemas/session" + }, + "type": "array" + }, "settingsProfileFormConfig": { "properties": { "action": { @@ -1881,9 +1917,9 @@ "in": "query", "name": "per_page", "schema": { - "default": 100, + "default": 250, "format": "int64", - "maximum": 500, + "maximum": 1000, "minimum": 1, "type": "integer" } @@ -2259,6 +2295,111 @@ ], "summary": "Calling this endpoint irrecoverably and permanently deletes and invalidates all sessions that belong to the given Identity.", "tags": ["v0alpha2"] + }, + "get": { + "description": "This endpoint is useful for:\n\nListing all sessions that belong to an Identity in an administrative context.", + "operationId": "adminListIdentitySessions", + "parameters": [ + { + "description": "ID is the identity's ID.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Items per Page\n\nThis is the number of items per page.", + "in": "query", + "name": "per_page", + "schema": { + "default": 250, + "format": "int64", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Pagination Page", + "in": "query", + "name": "page", + "schema": { + "default": 0, + "format": "int64", + "minimum": 0, + "type": "integer" + } + }, + { + "description": "Active is a boolean flag that filters out sessions based on the state. If no value is provided, all sessions are returned.", + "in": "query", + "name": "active", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sessionList" + } + } + }, + "description": "sessionList" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "security": [ + { + "oryAccessToken": [] + } + ], + "summary": "This endpoint returns all sessions that belong to the given Identity.", + "tags": ["v0alpha2"] } }, "/recovery/link": { @@ -2331,9 +2472,9 @@ "in": "query", "name": "per_page", "schema": { - "default": 100, + "default": 250, "format": "int64", - "maximum": 500, + "maximum": 1000, "minimum": 1, "type": "integer" } @@ -3966,6 +4107,183 @@ "tags": ["v0alpha2"] } }, + "/sessions": { + "delete": { + "description": "This endpoint is useful for:\n\nTo forcefully logout the current user from all other devices and sessions", + "operationId": "revokeSessions", + "parameters": [ + { + "description": "Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`.", + "in": "header", + "name": "X-Session-Token", + "schema": { + "type": "string" + } + }, + { + "description": "Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that\nscenario you must include the HTTP Cookie Header which originally was included in the request to your server.\nAn example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`.\n\nIt is ok if more than one cookie are included here as all other cookies will be ignored.", + "in": "header", + "name": "Cookie", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/revokedSessions" + } + } + }, + "description": "revokedSessions" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "Calling this endpoint invalidates all except the current session that belong to the logged-in user.\nSession data are not deleted.", + "tags": ["v0alpha2"] + }, + "get": { + "description": "This endpoint is useful for:\n\nDisplaying all other sessions that belong to the logged-in user", + "operationId": "listSessions", + "parameters": [ + { + "description": "Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`.", + "in": "header", + "name": "X-Session-Token", + "schema": { + "type": "string" + } + }, + { + "description": "Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that\nscenario you must include the HTTP Cookie Header which originally was included in the request to your server.\nAn example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`.\n\nIt is ok if more than one cookie are included here as all other cookies will be ignored.", + "in": "header", + "name": "Cookie", + "schema": { + "type": "string" + } + }, + { + "description": "Items per Page\n\nThis is the number of items per page.", + "in": "query", + "name": "per_page", + "schema": { + "default": 250, + "format": "int64", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Pagination Page", + "in": "query", + "name": "page", + "schema": { + "default": 0, + "format": "int64", + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sessionList" + } + } + }, + "description": "sessionList" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "This endpoints returns all other active sessions that belong to the logged-in user.\nThe current session can be retrieved by calling the `/sessions/whoami` endpoint.", + "tags": ["v0alpha2"] + } + }, "/sessions/whoami": { "get": { "description": "Uses the HTTP Headers in the GET request to determine (e.g. by using checking the cookies) who is authenticated.\nReturns a session object in the body or 401 if the credentials are invalid or no credentials were sent.\nAdditionally when the request it successful it adds the user ID to the 'X-Kratos-Authenticated-Identity-Id' header in the response.\n\nIf you call this endpoint from a server-side application, you must forward the HTTP Cookie Header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/protected-endpoint', async function (req, res) {\nconst session = await client.toSession(undefined, req.header('cookie'))\n\nconsole.log(session)\n})\n```\n\nWhen calling this endpoint from a non-browser application (e.g. mobile app) you must include the session token:\n\n```js\npseudo-code example\n...\nconst session = await client.toSession(\"the-session-token\")\n\nconsole.log(session)\n```\n\nDepending on your configuration this endpoint might return a 403 status code if the session has a lower Authenticator\nAssurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn\ncredentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user\nto sign in with the second factor or change the configuration.\n\nThis endpoint is useful for:\n\nAJAX calls. Remember to send credentials and set up CORS correctly!\nReverse proxies and API Gateways\nServer-side calls - use the `X-Session-Token` header!\n\nThis endpoint authenticates users by checking\n\nif the `Cookie` HTTP header was set containing an Ory Kratos Session Cookie;\nif the `Authorization: bearer \u003cory-session-token\u003e` HTTP header was set with a valid Ory Kratos Session Token;\nif the `X-Session-Token` HTTP header was set with a valid Ory Kratos Session Token.\n\nIf none of these headers are set or the cooke or token are invalid, the endpoint returns a HTTP 401 status code.\n\nAs explained above, this request may fail due to several reasons. The `error.id` can be one of:\n\n`session_inactive`: No active session was found in the request (e.g. no Ory Session Cookie / Ory Session Token).\n`session_aal2_required`: An active session was found but it does not fulfil the Authenticator Assurance Level, implying that the session must (e.g.) authenticate the second factor.", @@ -4036,6 +4354,60 @@ "tags": ["v0alpha2"] } }, + "/sessions/{id}": { + "delete": { + "description": "This endpoint is useful for:\n\nTo forcefully logout the current user from another device or session", + "operationId": "revokeSession", + "parameters": [ + { + "description": "ID is the session's ID.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "$ref": "#/components/responses/emptyResponse" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "Calling this endpoint invalidates the specified session. The current session cannot be revoked.\nSession data are not deleted.", + "tags": ["v0alpha2"] + } + }, "/version": { "get": { "description": "This endpoint returns the version of Ory Kratos.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the version will never\nrefer to the cluster state, only to a single instance.", diff --git a/docs/docs/guides/login-session.mdx b/docs/docs/guides/login-session.mdx index 947b9322df15..7dde6d3e4cfb 100644 --- a/docs/docs/guides/login-session.mdx +++ b/docs/docs/guides/login-session.mdx @@ -1,6 +1,6 @@ --- id: login-session -title: Configuring And Checking for Login Sessions +title: Configuring and Managing Login Sessions --- import CodeFromRemote from '@theme/CodeFromRemote' @@ -15,7 +15,7 @@ cookie or as a token, depending on the interaction type. A session is valid for the session lifespan you specify in the Ory Kratos config: -```yaml title="path/to/kratos/config.yml +```yaml title=path/to/kratos/config.yml session: lifespan: 720h # 30 days ``` @@ -23,7 +23,7 @@ session: Per default the session cookie has the `max-age` parameter set to the specified session lifespan. You may disable this behavior by setting: -```yaml title="path/to/kratos/config.yml +```yaml title=path/to/kratos/config.yml session: cookie: persistent: false @@ -110,8 +110,6 @@ A typical session payload will look like this: ]}> -{' '} - github.com/aeneasr/cupaloy/v2 v2.6.1-0.20210924214125-3dfdd01210a3 - github.com/gobuffalo/pop/v5 => github.com/gobuffalo/pop/v5 v5.3.4-0.20210608105745-bb07a373cc0e github.com/jackc/pgconn => github.com/jackc/pgconn v1.10.1-0.20211002123621-290ee79d1e8d github.com/knadh/koanf => github.com/aeneasr/koanf v0.14.1-0.20211230115640-aa3902b3267a github.com/luna-duclos/instrumentedsql => github.com/ory/instrumentedsql v1.2.0 @@ -42,7 +41,6 @@ require ( github.com/go-swagger/go-swagger v0.26.1 github.com/gobuffalo/fizz v1.14.0 github.com/gobuffalo/httptest v1.0.2 - github.com/gobuffalo/pop/v5 v5.3.4 github.com/gobuffalo/pop/v6 v6.0.1 github.com/gofrs/uuid v4.1.0+incompatible github.com/golang-jwt/jwt/v4 v4.1.0 diff --git a/go.sum b/go.sum index a7429f7adaac..e0349fa874ff 100644 --- a/go.sum +++ b/go.sum @@ -868,8 +868,10 @@ github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVD github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= github.com/gobuffalo/pop v4.13.1+incompatible h1:AhbqPxNOBN/DBb2DBaiBqzOXIBQXxEYzngHHJ+ytP4g= github.com/gobuffalo/pop v4.13.1+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= -github.com/gobuffalo/pop/v5 v5.3.4-0.20210608105745-bb07a373cc0e h1:TwTu/xo5+7Xc7PWas1NB35jwYv2ZIk+zlvA70bQnoNs= -github.com/gobuffalo/pop/v5 v5.3.4-0.20210608105745-bb07a373cc0e/go.mod h1:UiVurv2aTKC7MuR27PnMrQjAazoLr8SoC/LuTKTS/tQ= +github.com/gobuffalo/pop/v5 v5.0.11/go.mod h1:mZJHJbA3cy2V18abXYuVop2ldEJ8UZ2DK6qOekC5u5g= +github.com/gobuffalo/pop/v5 v5.2.0/go.mod h1:Hj586Cr7FoTFNmvzyNdUcajv3r0A+W+bkil4RIX/zKo= +github.com/gobuffalo/pop/v5 v5.3.1/go.mod h1:vcEDhh6cJ3WVENqJDFt/6z7zNb7lLnlN8vj3n5G9rYA= +github.com/gobuffalo/pop/v5 v5.3.3/go.mod h1:Ey1hqzDLkWQKNEfsnafaz+3P1h/TrS++W9PmpGsNxvk= github.com/gobuffalo/pop/v6 v6.0.0/go.mod h1:5rd3OnViLhjteR8+0i/mT9Q4CzkTzCoR7tm/9mmAic4= github.com/gobuffalo/pop/v6 v6.0.1 h1:4YhzxW4hVvf0xLW9zVkhPZFuH5VmBc4ffIIP/C++SLQ= github.com/gobuffalo/pop/v6 v6.0.1/go.mod h1:5NO7ehmyRjRctnbMDhIqKkkg6zvdueufYltxErfp9BU= @@ -1197,7 +1199,6 @@ github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLD github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -github.com/jackc/pgtype v1.7.0/go.mod h1:ZnHF+rMePVqDKaOfJVI4Q8IVvAQMryDlDkZnKOI75BE= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= @@ -1213,7 +1214,6 @@ github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oA github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -github.com/jackc/pgx/v4 v4.11.0/go.mod h1:i62xJgdrtVDsnL3U8ekyrQXEwGNTRoG7/8r+CIdYfcc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= @@ -1241,7 +1241,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfC github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4= @@ -1315,7 +1314,6 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -2169,6 +2167,7 @@ golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/identity/handler.go b/identity/handler.go index 7c28d2614712..92a9adcf6039 100644 --- a/identity/handler.go +++ b/identity/handler.go @@ -77,24 +77,7 @@ type identityList []Identity // swagger:parameters adminListIdentities // nolint:deadcode,unused type adminListIdentities struct { - // Items per Page - // - // This is the number of items per page. - // - // required: false - // in: query - // default: 100 - // min: 1 - // max: 500 - PerPage int `json:"per_page"` - - // Pagination Page - // - // required: false - // in: query - // default: 0 - // min: 0 - Page int `json:"page"` + x.PaginationParams } // swagger:route GET /identities v0alpha2 adminListIdentities diff --git a/internal/httpclient/.openapi-generator/FILES b/internal/httpclient/.openapi-generator/FILES index 266b1a529acc..9fbe63f65eed 100644 --- a/internal/httpclient/.openapi-generator/FILES +++ b/internal/httpclient/.openapi-generator/FILES @@ -26,7 +26,9 @@ docs/InlineResponse503.md docs/JsonError.md docs/MetadataApi.md docs/NeedsPrivilegedSessionError.md +docs/Pagination.md docs/RecoveryAddress.md +docs/RevokedSessions.md docs/SelfServiceBrowserLocationChangeRequiredError.md docs/SelfServiceError.md docs/SelfServiceFlowExpiredError.md @@ -101,7 +103,9 @@ model_inline_response_200_1.go model_inline_response_503.go model_json_error.go model_needs_privileged_session_error.go +model_pagination.go model_recovery_address.go +model_revoked_sessions.go model_self_service_browser_location_change_required_error.go model_self_service_error.go model_self_service_flow_expired_error.go diff --git a/internal/httpclient/README.md b/internal/httpclient/README.md index 6df00ecda261..e4b3ee919897 100644 --- a/internal/httpclient/README.md +++ b/internal/httpclient/README.md @@ -92,6 +92,7 @@ Class | Method | HTTP request | Description *V0alpha2Api* | [**AdminDeleteIdentitySessions**](docs/V0alpha2Api.md#admindeleteidentitysessions) | **Delete** /identities/{id}/sessions | Calling this endpoint irrecoverably and permanently deletes and invalidates all sessions that belong to the given Identity. *V0alpha2Api* | [**AdminGetIdentity**](docs/V0alpha2Api.md#admingetidentity) | **Get** /identities/{id} | Get an Identity *V0alpha2Api* | [**AdminListIdentities**](docs/V0alpha2Api.md#adminlistidentities) | **Get** /identities | List Identities +*V0alpha2Api* | [**AdminListIdentitySessions**](docs/V0alpha2Api.md#adminlistidentitysessions) | **Get** /identities/{id}/sessions | This endpoint returns all sessions that belong to the given Identity. *V0alpha2Api* | [**AdminUpdateIdentity**](docs/V0alpha2Api.md#adminupdateidentity) | **Put** /identities/{id} | Update an Identity *V0alpha2Api* | [**CreateSelfServiceLogoutFlowUrlForBrowsers**](docs/V0alpha2Api.md#createselfservicelogoutflowurlforbrowsers) | **Get** /self-service/logout/browser | Create a Logout URL for Browsers *V0alpha2Api* | [**GetJsonSchema**](docs/V0alpha2Api.md#getjsonschema) | **Get** /schemas/{id} | @@ -113,6 +114,9 @@ Class | Method | HTTP request | Description *V0alpha2Api* | [**InitializeSelfServiceVerificationFlowForBrowsers**](docs/V0alpha2Api.md#initializeselfserviceverificationflowforbrowsers) | **Get** /self-service/verification/browser | Initialize Verification Flow for Browser Clients *V0alpha2Api* | [**InitializeSelfServiceVerificationFlowWithoutBrowser**](docs/V0alpha2Api.md#initializeselfserviceverificationflowwithoutbrowser) | **Get** /self-service/verification/api | Initialize Verification Flow for APIs, Services, Apps, ... *V0alpha2Api* | [**ListIdentitySchemas**](docs/V0alpha2Api.md#listidentityschemas) | **Get** /schemas | +*V0alpha2Api* | [**ListSessions**](docs/V0alpha2Api.md#listsessions) | **Get** /sessions | This endpoints returns all other active sessions that belong to the logged-in user. The current session can be retrieved by calling the `/sessions/whoami` endpoint. +*V0alpha2Api* | [**RevokeSession**](docs/V0alpha2Api.md#revokesession) | **Delete** /sessions/{id} | Calling this endpoint invalidates the specified session. The current session cannot be revoked. Session data are not deleted. +*V0alpha2Api* | [**RevokeSessions**](docs/V0alpha2Api.md#revokesessions) | **Delete** /sessions | Calling this endpoint invalidates all except the current session that belong to the logged-in user. Session data are not deleted. *V0alpha2Api* | [**SubmitSelfServiceLoginFlow**](docs/V0alpha2Api.md#submitselfserviceloginflow) | **Post** /self-service/login | Submit a Login Flow *V0alpha2Api* | [**SubmitSelfServiceLogoutFlow**](docs/V0alpha2Api.md#submitselfservicelogoutflow) | **Get** /self-service/logout | Complete Self-Service Logout *V0alpha2Api* | [**SubmitSelfServiceLogoutFlowWithoutBrowser**](docs/V0alpha2Api.md#submitselfservicelogoutflowwithoutbrowser) | **Delete** /self-service/logout/api | Perform Logout for APIs, Services, Apps, ... @@ -143,7 +147,9 @@ Class | Method | HTTP request | Description - [InlineResponse503](docs/InlineResponse503.md) - [JsonError](docs/JsonError.md) - [NeedsPrivilegedSessionError](docs/NeedsPrivilegedSessionError.md) + - [Pagination](docs/Pagination.md) - [RecoveryAddress](docs/RecoveryAddress.md) + - [RevokedSessions](docs/RevokedSessions.md) - [SelfServiceBrowserLocationChangeRequiredError](docs/SelfServiceBrowserLocationChangeRequiredError.md) - [SelfServiceError](docs/SelfServiceError.md) - [SelfServiceFlowExpiredError](docs/SelfServiceFlowExpiredError.md) diff --git a/internal/httpclient/api/openapi.yaml b/internal/httpclient/api/openapi.yaml index 7e7e8751a0b7..dea8f28c55cf 100644 --- a/internal/httpclient/api/openapi.yaml +++ b/internal/httpclient/api/openapi.yaml @@ -110,9 +110,9 @@ paths: name: per_page required: false schema: - default: 100 + default: 250 format: int64 - maximum: 500 + maximum: 1000 minimum: 1 type: integer style: form @@ -388,6 +388,92 @@ paths: all sessions that belong to the given Identity. tags: - v0alpha2 + get: + description: |- + This endpoint is useful for: + + Listing all sessions that belong to an Identity in an administrative context. + operationId: adminListIdentitySessions + parameters: + - description: ID is the identity's ID. + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + - description: |- + Items per Page + + This is the number of items per page. + explode: true + in: query + name: per_page + required: false + schema: + default: 250 + format: int64 + maximum: 1000 + minimum: 1 + type: integer + style: form + - description: Pagination Page + explode: true + in: query + name: page + required: false + schema: + default: 0 + format: int64 + minimum: 0 + type: integer + style: form + - description: Active is a boolean flag that filters out sessions based on the + state. If no value is provided, all sessions are returned. + explode: true + in: query + name: active + required: false + schema: + type: boolean + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/sessionList' + description: sessionList + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + security: + - oryAccessToken: [] + summary: This endpoint returns all sessions that belong to the given Identity. + tags: + - v0alpha2 /recovery/link: post: description: |- @@ -442,9 +528,9 @@ paths: name: per_page required: false schema: - default: 100 + default: 250 format: int64 - maximum: 500 + maximum: 1000 minimum: 1 type: integer style: form @@ -2152,6 +2238,163 @@ paths: summary: Get Verification Flow tags: - v0alpha2 + /sessions: + delete: + description: |- + This endpoint is useful for: + + To forcefully logout the current user from all other devices and sessions + operationId: revokeSessions + parameters: + - description: Set the Session Token when calling from non-browser clients. + A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. + explode: false + in: header + name: X-Session-Token + required: false + schema: + type: string + style: simple + - description: |- + Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that + scenario you must include the HTTP Cookie Header which originally was included in the request to your server. + An example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`. + + It is ok if more than one cookie are included here as all other cookies will be ignored. + explode: false + in: header + name: Cookie + required: false + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/revokedSessions' + description: revokedSessions + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + summary: |- + Calling this endpoint invalidates all except the current session that belong to the logged-in user. + Session data are not deleted. + tags: + - v0alpha2 + get: + description: |- + This endpoint is useful for: + + Displaying all other sessions that belong to the logged-in user + operationId: listSessions + parameters: + - description: Set the Session Token when calling from non-browser clients. + A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. + explode: false + in: header + name: X-Session-Token + required: false + schema: + type: string + style: simple + - description: |- + Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that + scenario you must include the HTTP Cookie Header which originally was included in the request to your server. + An example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`. + + It is ok if more than one cookie are included here as all other cookies will be ignored. + explode: false + in: header + name: Cookie + required: false + schema: + type: string + style: simple + - description: |- + Items per Page + + This is the number of items per page. + explode: true + in: query + name: per_page + required: false + schema: + default: 250 + format: int64 + maximum: 1000 + minimum: 1 + type: integer + style: form + - description: Pagination Page + explode: true + in: query + name: page + required: false + schema: + default: 0 + format: int64 + minimum: 0 + type: integer + style: form + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/sessionList' + description: sessionList + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "404": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + summary: |- + This endpoints returns all other active sessions that belong to the logged-in user. + The current session can be retrieved by calling the `/sessions/whoami` endpoint. + tags: + - v0alpha2 /sessions/whoami: get: description: |- @@ -2257,6 +2500,49 @@ paths: summary: Check Who the Current HTTP Session Belongs To tags: - v0alpha2 + /sessions/{id}: + delete: + description: |- + This endpoint is useful for: + + To forcefully logout the current user from another device or session + operationId: revokeSession + parameters: + - description: ID is the session's ID. + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + responses: + "204": + description: Empty responses are sent when, for example, resources are deleted. + The HTTP status code for empty responses is typically 201. + "400": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "401": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + "500": + content: + application/json: + schema: + $ref: '#/components/schemas/jsonError' + description: jsonError + summary: |- + Calling this endpoint invalidates the specified session. The current session cannot be revoked. + Session data are not deleted. + tags: + - v0alpha2 /version: get: description: |- @@ -2787,6 +3073,34 @@ components: format: date-time title: NullTime implements sql.NullTime functionality. type: string + pagination: + properties: + page: + default: 0 + description: Pagination Page + format: int64 + minimum: 0 + type: integer + per_page: + default: 250 + description: |- + Items per Page + + This is the number of items per page. + format: int64 + maximum: 1000 + minimum: 1 + type: integer + type: object + revokedSessions: + example: + count: 0 + properties: + count: + description: The number of sessions that were revoked. + format: int64 + type: integer + type: object selfServiceBrowserLocationChangeRequiredError: properties: code: @@ -3707,6 +4021,10 @@ components: description: UserAgent of this device type: string type: object + sessionList: + items: + $ref: '#/components/schemas/session' + type: array settingsProfileFormConfig: properties: action: diff --git a/internal/httpclient/api_v0alpha2.go b/internal/httpclient/api_v0alpha2.go index fe47c787540d..ba815408f974 100644 --- a/internal/httpclient/api_v0alpha2.go +++ b/internal/httpclient/api_v0alpha2.go @@ -125,6 +125,23 @@ type V0alpha2Api interface { */ AdminListIdentitiesExecute(r V0alpha2ApiApiAdminListIdentitiesRequest) ([]Identity, *http.Response, error) + /* + * AdminListIdentitySessions This endpoint returns all sessions that belong to the given Identity. + * This endpoint is useful for: + + Listing all sessions that belong to an Identity in an administrative context. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id ID is the identity's ID. + * @return V0alpha2ApiApiAdminListIdentitySessionsRequest + */ + AdminListIdentitySessions(ctx context.Context, id string) V0alpha2ApiApiAdminListIdentitySessionsRequest + + /* + * AdminListIdentitySessionsExecute executes the request + * @return []Session + */ + AdminListIdentitySessionsExecute(r V0alpha2ApiApiAdminListIdentitySessionsRequest) ([]Session, *http.Response, error) + /* * AdminUpdateIdentity Update an Identity * This endpoint updates an identity. It is NOT possible to set an identity's credentials (password, ...) @@ -708,6 +725,54 @@ type V0alpha2Api interface { */ ListIdentitySchemasExecute(r V0alpha2ApiApiListIdentitySchemasRequest) ([]IdentitySchema, *http.Response, error) + /* + * ListSessions This endpoints returns all other active sessions that belong to the logged-in user. The current session can be retrieved by calling the `/sessions/whoami` endpoint. + * This endpoint is useful for: + + Displaying all other sessions that belong to the logged-in user + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return V0alpha2ApiApiListSessionsRequest + */ + ListSessions(ctx context.Context) V0alpha2ApiApiListSessionsRequest + + /* + * ListSessionsExecute executes the request + * @return []Session + */ + ListSessionsExecute(r V0alpha2ApiApiListSessionsRequest) ([]Session, *http.Response, error) + + /* + * RevokeSession Calling this endpoint invalidates the specified session. The current session cannot be revoked. Session data are not deleted. + * This endpoint is useful for: + + To forcefully logout the current user from another device or session + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id ID is the session's ID. + * @return V0alpha2ApiApiRevokeSessionRequest + */ + RevokeSession(ctx context.Context, id string) V0alpha2ApiApiRevokeSessionRequest + + /* + * RevokeSessionExecute executes the request + */ + RevokeSessionExecute(r V0alpha2ApiApiRevokeSessionRequest) (*http.Response, error) + + /* + * RevokeSessions Calling this endpoint invalidates all except the current session that belong to the logged-in user. Session data are not deleted. + * This endpoint is useful for: + + To forcefully logout the current user from all other devices and sessions + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return V0alpha2ApiApiRevokeSessionsRequest + */ + RevokeSessions(ctx context.Context) V0alpha2ApiApiRevokeSessionsRequest + + /* + * RevokeSessionsExecute executes the request + * @return RevokedSessions + */ + RevokeSessionsExecute(r V0alpha2ApiApiRevokeSessionsRequest) (*RevokedSessions, *http.Response, error) + /* * SubmitSelfServiceLoginFlow Submit a Login Flow * :::info @@ -1891,6 +1956,191 @@ func (a *V0alpha2ApiService) AdminListIdentitiesExecute(r V0alpha2ApiApiAdminLis return localVarReturnValue, localVarHTTPResponse, nil } +type V0alpha2ApiApiAdminListIdentitySessionsRequest struct { + ctx context.Context + ApiService V0alpha2Api + id string + perPage *int64 + page *int64 + active *bool +} + +func (r V0alpha2ApiApiAdminListIdentitySessionsRequest) PerPage(perPage int64) V0alpha2ApiApiAdminListIdentitySessionsRequest { + r.perPage = &perPage + return r +} +func (r V0alpha2ApiApiAdminListIdentitySessionsRequest) Page(page int64) V0alpha2ApiApiAdminListIdentitySessionsRequest { + r.page = &page + return r +} +func (r V0alpha2ApiApiAdminListIdentitySessionsRequest) Active(active bool) V0alpha2ApiApiAdminListIdentitySessionsRequest { + r.active = &active + return r +} + +func (r V0alpha2ApiApiAdminListIdentitySessionsRequest) Execute() ([]Session, *http.Response, error) { + return r.ApiService.AdminListIdentitySessionsExecute(r) +} + +/* + * AdminListIdentitySessions This endpoint returns all sessions that belong to the given Identity. + * This endpoint is useful for: + +Listing all sessions that belong to an Identity in an administrative context. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id ID is the identity's ID. + * @return V0alpha2ApiApiAdminListIdentitySessionsRequest +*/ +func (a *V0alpha2ApiService) AdminListIdentitySessions(ctx context.Context, id string) V0alpha2ApiApiAdminListIdentitySessionsRequest { + return V0alpha2ApiApiAdminListIdentitySessionsRequest{ + ApiService: a, + ctx: ctx, + id: id, + } +} + +/* + * Execute executes the request + * @return []Session + */ +func (a *V0alpha2ApiService) AdminListIdentitySessionsExecute(r V0alpha2ApiApiAdminListIdentitySessionsRequest) ([]Session, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []Session + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "V0alpha2ApiService.AdminListIdentitySessions") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/identities/{id}/sessions" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterToString(r.id, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + if r.perPage != nil { + localVarQueryParams.Add("per_page", parameterToString(*r.perPage, "")) + } + if r.page != nil { + localVarQueryParams.Add("page", parameterToString(*r.page, "")) + } + if r.active != nil { + localVarQueryParams.Add("active", parameterToString(*r.active, "")) + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.ctx != nil { + // API Key Authentication + if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok { + if apiKey, ok := auth["oryAccessToken"]; ok { + var key string + if apiKey.Prefix != "" { + key = apiKey.Prefix + " " + apiKey.Key + } else { + key = apiKey.Key + } + localVarHeaderParams["Authorization"] = key + } + } + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = ioutil.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 401 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type V0alpha2ApiApiAdminUpdateIdentityRequest struct { ctx context.Context ApiService V0alpha2Api @@ -5048,6 +5298,465 @@ func (a *V0alpha2ApiService) ListIdentitySchemasExecute(r V0alpha2ApiApiListIden return localVarReturnValue, localVarHTTPResponse, nil } +type V0alpha2ApiApiListSessionsRequest struct { + ctx context.Context + ApiService V0alpha2Api + xSessionToken *string + cookie *string + perPage *int64 + page *int64 +} + +func (r V0alpha2ApiApiListSessionsRequest) XSessionToken(xSessionToken string) V0alpha2ApiApiListSessionsRequest { + r.xSessionToken = &xSessionToken + return r +} +func (r V0alpha2ApiApiListSessionsRequest) Cookie(cookie string) V0alpha2ApiApiListSessionsRequest { + r.cookie = &cookie + return r +} +func (r V0alpha2ApiApiListSessionsRequest) PerPage(perPage int64) V0alpha2ApiApiListSessionsRequest { + r.perPage = &perPage + return r +} +func (r V0alpha2ApiApiListSessionsRequest) Page(page int64) V0alpha2ApiApiListSessionsRequest { + r.page = &page + return r +} + +func (r V0alpha2ApiApiListSessionsRequest) Execute() ([]Session, *http.Response, error) { + return r.ApiService.ListSessionsExecute(r) +} + +/* + * ListSessions This endpoints returns all other active sessions that belong to the logged-in user. The current session can be retrieved by calling the `/sessions/whoami` endpoint. + * This endpoint is useful for: + +Displaying all other sessions that belong to the logged-in user + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return V0alpha2ApiApiListSessionsRequest +*/ +func (a *V0alpha2ApiService) ListSessions(ctx context.Context) V0alpha2ApiApiListSessionsRequest { + return V0alpha2ApiApiListSessionsRequest{ + ApiService: a, + ctx: ctx, + } +} + +/* + * Execute executes the request + * @return []Session + */ +func (a *V0alpha2ApiService) ListSessionsExecute(r V0alpha2ApiApiListSessionsRequest) ([]Session, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue []Session + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "V0alpha2ApiService.ListSessions") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/sessions" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + if r.perPage != nil { + localVarQueryParams.Add("per_page", parameterToString(*r.perPage, "")) + } + if r.page != nil { + localVarQueryParams.Add("page", parameterToString(*r.page, "")) + } + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.xSessionToken != nil { + localVarHeaderParams["X-Session-Token"] = parameterToString(*r.xSessionToken, "") + } + if r.cookie != nil { + localVarHeaderParams["Cookie"] = parameterToString(*r.cookie, "") + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = ioutil.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 401 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + +type V0alpha2ApiApiRevokeSessionRequest struct { + ctx context.Context + ApiService V0alpha2Api + id string +} + +func (r V0alpha2ApiApiRevokeSessionRequest) Execute() (*http.Response, error) { + return r.ApiService.RevokeSessionExecute(r) +} + +/* + * RevokeSession Calling this endpoint invalidates the specified session. The current session cannot be revoked. Session data are not deleted. + * This endpoint is useful for: + +To forcefully logout the current user from another device or session + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id ID is the session's ID. + * @return V0alpha2ApiApiRevokeSessionRequest +*/ +func (a *V0alpha2ApiService) RevokeSession(ctx context.Context, id string) V0alpha2ApiApiRevokeSessionRequest { + return V0alpha2ApiApiRevokeSessionRequest{ + ApiService: a, + ctx: ctx, + id: id, + } +} + +/* + * Execute executes the request + */ +func (a *V0alpha2ApiService) RevokeSessionExecute(r V0alpha2ApiApiRevokeSessionRequest) (*http.Response, error) { + var ( + localVarHTTPMethod = http.MethodDelete + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "V0alpha2ApiService.RevokeSession") + if err != nil { + return nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/sessions/{id}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterToString(r.id, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarHTTPResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = ioutil.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 401 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + return localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarHTTPResponse, newErr + } + + return localVarHTTPResponse, nil +} + +type V0alpha2ApiApiRevokeSessionsRequest struct { + ctx context.Context + ApiService V0alpha2Api + xSessionToken *string + cookie *string +} + +func (r V0alpha2ApiApiRevokeSessionsRequest) XSessionToken(xSessionToken string) V0alpha2ApiApiRevokeSessionsRequest { + r.xSessionToken = &xSessionToken + return r +} +func (r V0alpha2ApiApiRevokeSessionsRequest) Cookie(cookie string) V0alpha2ApiApiRevokeSessionsRequest { + r.cookie = &cookie + return r +} + +func (r V0alpha2ApiApiRevokeSessionsRequest) Execute() (*RevokedSessions, *http.Response, error) { + return r.ApiService.RevokeSessionsExecute(r) +} + +/* + * RevokeSessions Calling this endpoint invalidates all except the current session that belong to the logged-in user. Session data are not deleted. + * This endpoint is useful for: + +To forcefully logout the current user from all other devices and sessions + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @return V0alpha2ApiApiRevokeSessionsRequest +*/ +func (a *V0alpha2ApiService) RevokeSessions(ctx context.Context) V0alpha2ApiApiRevokeSessionsRequest { + return V0alpha2ApiApiRevokeSessionsRequest{ + ApiService: a, + ctx: ctx, + } +} + +/* + * Execute executes the request + * @return RevokedSessions + */ +func (a *V0alpha2ApiService) RevokeSessionsExecute(r V0alpha2ApiApiRevokeSessionsRequest) (*RevokedSessions, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodDelete + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *RevokedSessions + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "V0alpha2ApiService.RevokeSessions") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/sessions" + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.xSessionToken != nil { + localVarHeaderParams["X-Session-Token"] = parameterToString(*r.xSessionToken, "") + } + if r.cookie != nil { + localVarHeaderParams["Cookie"] = parameterToString(*r.cookie, "") + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := ioutil.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = ioutil.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 401 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 404 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + if localVarHTTPResponse.StatusCode == 500 { + var v JsonError + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type V0alpha2ApiApiSubmitSelfServiceLoginFlowRequest struct { ctx context.Context ApiService V0alpha2Api diff --git a/internal/httpclient/docs/Pagination.md b/internal/httpclient/docs/Pagination.md new file mode 100644 index 000000000000..79e11a031312 --- /dev/null +++ b/internal/httpclient/docs/Pagination.md @@ -0,0 +1,82 @@ +# Pagination + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Page** | Pointer to **int64** | Pagination Page | [optional] [default to 0] +**PerPage** | Pointer to **int64** | Items per Page This is the number of items per page. | [optional] [default to 250] + +## Methods + +### NewPagination + +`func NewPagination() *Pagination` + +NewPagination instantiates a new Pagination object +This constructor will assign default values to properties that have it defined, +and makes sure properties required by API are set, but the set of arguments +will change when the set of required properties is changed + +### NewPaginationWithDefaults + +`func NewPaginationWithDefaults() *Pagination` + +NewPaginationWithDefaults instantiates a new Pagination object +This constructor will only assign default values to properties that have it defined, +but it doesn't guarantee that properties required by API are set + +### GetPage + +`func (o *Pagination) GetPage() int64` + +GetPage returns the Page field if non-nil, zero value otherwise. + +### GetPageOk + +`func (o *Pagination) GetPageOk() (*int64, bool)` + +GetPageOk returns a tuple with the Page field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetPage + +`func (o *Pagination) SetPage(v int64)` + +SetPage sets Page field to given value. + +### HasPage + +`func (o *Pagination) HasPage() bool` + +HasPage returns a boolean if a field has been set. + +### GetPerPage + +`func (o *Pagination) GetPerPage() int64` + +GetPerPage returns the PerPage field if non-nil, zero value otherwise. + +### GetPerPageOk + +`func (o *Pagination) GetPerPageOk() (*int64, bool)` + +GetPerPageOk returns a tuple with the PerPage field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetPerPage + +`func (o *Pagination) SetPerPage(v int64)` + +SetPerPage sets PerPage field to given value. + +### HasPerPage + +`func (o *Pagination) HasPerPage() bool` + +HasPerPage returns a boolean if a field has been set. + + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/internal/httpclient/docs/RevokedSessions.md b/internal/httpclient/docs/RevokedSessions.md new file mode 100644 index 000000000000..a42d4bfc2f6d --- /dev/null +++ b/internal/httpclient/docs/RevokedSessions.md @@ -0,0 +1,56 @@ +# RevokedSessions + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**Count** | Pointer to **int64** | The number of sessions that were revoked. | [optional] + +## Methods + +### NewRevokedSessions + +`func NewRevokedSessions() *RevokedSessions` + +NewRevokedSessions instantiates a new RevokedSessions object +This constructor will assign default values to properties that have it defined, +and makes sure properties required by API are set, but the set of arguments +will change when the set of required properties is changed + +### NewRevokedSessionsWithDefaults + +`func NewRevokedSessionsWithDefaults() *RevokedSessions` + +NewRevokedSessionsWithDefaults instantiates a new RevokedSessions object +This constructor will only assign default values to properties that have it defined, +but it doesn't guarantee that properties required by API are set + +### GetCount + +`func (o *RevokedSessions) GetCount() int64` + +GetCount returns the Count field if non-nil, zero value otherwise. + +### GetCountOk + +`func (o *RevokedSessions) GetCountOk() (*int64, bool)` + +GetCountOk returns a tuple with the Count field if it's non-nil, zero value otherwise +and a boolean to check if the value has been set. + +### SetCount + +`func (o *RevokedSessions) SetCount(v int64)` + +SetCount sets Count field to given value. + +### HasCount + +`func (o *RevokedSessions) HasCount() bool` + +HasCount returns a boolean if a field has been set. + + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + + diff --git a/internal/httpclient/docs/V0alpha2Api.md b/internal/httpclient/docs/V0alpha2Api.md index 3f8b57a5549a..32d67a29070b 100644 --- a/internal/httpclient/docs/V0alpha2Api.md +++ b/internal/httpclient/docs/V0alpha2Api.md @@ -10,6 +10,7 @@ Method | HTTP request | Description [**AdminDeleteIdentitySessions**](V0alpha2Api.md#AdminDeleteIdentitySessions) | **Delete** /identities/{id}/sessions | Calling this endpoint irrecoverably and permanently deletes and invalidates all sessions that belong to the given Identity. [**AdminGetIdentity**](V0alpha2Api.md#AdminGetIdentity) | **Get** /identities/{id} | Get an Identity [**AdminListIdentities**](V0alpha2Api.md#AdminListIdentities) | **Get** /identities | List Identities +[**AdminListIdentitySessions**](V0alpha2Api.md#AdminListIdentitySessions) | **Get** /identities/{id}/sessions | This endpoint returns all sessions that belong to the given Identity. [**AdminUpdateIdentity**](V0alpha2Api.md#AdminUpdateIdentity) | **Put** /identities/{id} | Update an Identity [**CreateSelfServiceLogoutFlowUrlForBrowsers**](V0alpha2Api.md#CreateSelfServiceLogoutFlowUrlForBrowsers) | **Get** /self-service/logout/browser | Create a Logout URL for Browsers [**GetJsonSchema**](V0alpha2Api.md#GetJsonSchema) | **Get** /schemas/{id} | @@ -31,6 +32,9 @@ Method | HTTP request | Description [**InitializeSelfServiceVerificationFlowForBrowsers**](V0alpha2Api.md#InitializeSelfServiceVerificationFlowForBrowsers) | **Get** /self-service/verification/browser | Initialize Verification Flow for Browser Clients [**InitializeSelfServiceVerificationFlowWithoutBrowser**](V0alpha2Api.md#InitializeSelfServiceVerificationFlowWithoutBrowser) | **Get** /self-service/verification/api | Initialize Verification Flow for APIs, Services, Apps, ... [**ListIdentitySchemas**](V0alpha2Api.md#ListIdentitySchemas) | **Get** /schemas | +[**ListSessions**](V0alpha2Api.md#ListSessions) | **Get** /sessions | This endpoints returns all other active sessions that belong to the logged-in user. The current session can be retrieved by calling the `/sessions/whoami` endpoint. +[**RevokeSession**](V0alpha2Api.md#RevokeSession) | **Delete** /sessions/{id} | Calling this endpoint invalidates the specified session. The current session cannot be revoked. Session data are not deleted. +[**RevokeSessions**](V0alpha2Api.md#RevokeSessions) | **Delete** /sessions | Calling this endpoint invalidates all except the current session that belong to the logged-in user. Session data are not deleted. [**SubmitSelfServiceLoginFlow**](V0alpha2Api.md#SubmitSelfServiceLoginFlow) | **Post** /self-service/login | Submit a Login Flow [**SubmitSelfServiceLogoutFlow**](V0alpha2Api.md#SubmitSelfServiceLogoutFlow) | **Get** /self-service/logout | Complete Self-Service Logout [**SubmitSelfServiceLogoutFlowWithoutBrowser**](V0alpha2Api.md#SubmitSelfServiceLogoutFlowWithoutBrowser) | **Delete** /self-service/logout/api | Perform Logout for APIs, Services, Apps, ... @@ -403,7 +407,7 @@ import ( ) func main() { - perPage := int64(789) // int64 | Items per Page This is the number of items per page. (optional) (default to 100) + perPage := int64(789) // int64 | Items per Page This is the number of items per page. (optional) (default to 250) page := int64(789) // int64 | Pagination Page (optional) (default to 0) configuration := openapiclient.NewConfiguration() @@ -429,7 +433,7 @@ Other parameters are passed through a pointer to a apiAdminListIdentitiesRequest Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **perPage** | **int64** | Items per Page This is the number of items per page. | [default to 100] + **perPage** | **int64** | Items per Page This is the number of items per page. | [default to 250] **page** | **int64** | Pagination Page | [default to 0] ### Return type @@ -450,6 +454,82 @@ Name | Type | Description | Notes [[Back to README]](../README.md) +## AdminListIdentitySessions + +> []Session AdminListIdentitySessions(ctx, id).PerPage(perPage).Page(page).Active(active).Execute() + +This endpoint returns all sessions that belong to the given Identity. + + + +### Example + +```go +package main + +import ( + "context" + "fmt" + "os" + openapiclient "./openapi" +) + +func main() { + id := "id_example" // string | ID is the identity's ID. + perPage := int64(789) // int64 | Items per Page This is the number of items per page. (optional) (default to 250) + page := int64(789) // int64 | Pagination Page (optional) (default to 0) + active := true // bool | Active is a boolean flag that filters out sessions based on the state. If no value is provided, all sessions are returned. (optional) + + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + resp, r, err := apiClient.V0alpha2Api.AdminListIdentitySessions(context.Background(), id).PerPage(perPage).Page(page).Active(active).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `V0alpha2Api.AdminListIdentitySessions``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } + // response from `AdminListIdentitySessions`: []Session + fmt.Fprintf(os.Stdout, "Response from `V0alpha2Api.AdminListIdentitySessions`: %v\n", resp) +} +``` + +### Path Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. +**id** | **string** | ID is the identity's ID. | + +### Other Parameters + +Other parameters are passed through a pointer to a apiAdminListIdentitySessionsRequest struct via the builder pattern + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + + **perPage** | **int64** | Items per Page This is the number of items per page. | [default to 250] + **page** | **int64** | Pagination Page | [default to 0] + **active** | **bool** | Active is a boolean flag that filters out sessions based on the state. If no value is provided, all sessions are returned. | + +### Return type + +[**[]Session**](Session.md) + +### Authorization + +[oryAccessToken](../README.md#oryAccessToken) + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + + ## AdminUpdateIdentity > Identity AdminUpdateIdentity(ctx, id).AdminUpdateIdentityBody(adminUpdateIdentityBody).Execute() @@ -1801,7 +1881,7 @@ import ( ) func main() { - perPage := int64(789) // int64 | Items per Page This is the number of items per page. (optional) (default to 100) + perPage := int64(789) // int64 | Items per Page This is the number of items per page. (optional) (default to 250) page := int64(789) // int64 | Pagination Page (optional) (default to 0) configuration := openapiclient.NewConfiguration() @@ -1827,7 +1907,7 @@ Other parameters are passed through a pointer to a apiListIdentitySchemasRequest Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **perPage** | **int64** | Items per Page This is the number of items per page. | [default to 100] + **perPage** | **int64** | Items per Page This is the number of items per page. | [default to 250] **page** | **int64** | Pagination Page | [default to 0] ### Return type @@ -1848,6 +1928,214 @@ No authorization required [[Back to README]](../README.md) +## ListSessions + +> []Session ListSessions(ctx).XSessionToken(xSessionToken).Cookie(cookie).PerPage(perPage).Page(page).Execute() + +This endpoints returns all other active sessions that belong to the logged-in user. The current session can be retrieved by calling the `/sessions/whoami` endpoint. + + + +### Example + +```go +package main + +import ( + "context" + "fmt" + "os" + openapiclient "./openapi" +) + +func main() { + xSessionToken := "xSessionToken_example" // string | Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. (optional) + cookie := "cookie_example" // string | Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that scenario you must include the HTTP Cookie Header which originally was included in the request to your server. An example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`. It is ok if more than one cookie are included here as all other cookies will be ignored. (optional) + perPage := int64(789) // int64 | Items per Page This is the number of items per page. (optional) (default to 250) + page := int64(789) // int64 | Pagination Page (optional) (default to 0) + + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + resp, r, err := apiClient.V0alpha2Api.ListSessions(context.Background()).XSessionToken(xSessionToken).Cookie(cookie).PerPage(perPage).Page(page).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `V0alpha2Api.ListSessions``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } + // response from `ListSessions`: []Session + fmt.Fprintf(os.Stdout, "Response from `V0alpha2Api.ListSessions`: %v\n", resp) +} +``` + +### Path Parameters + + + +### Other Parameters + +Other parameters are passed through a pointer to a apiListSessionsRequest struct via the builder pattern + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **xSessionToken** | **string** | Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. | + **cookie** | **string** | Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that scenario you must include the HTTP Cookie Header which originally was included in the request to your server. An example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`. It is ok if more than one cookie are included here as all other cookies will be ignored. | + **perPage** | **int64** | Items per Page This is the number of items per page. | [default to 250] + **page** | **int64** | Pagination Page | [default to 0] + +### Return type + +[**[]Session**](Session.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + + +## RevokeSession + +> RevokeSession(ctx, id).Execute() + +Calling this endpoint invalidates the specified session. The current session cannot be revoked. Session data are not deleted. + + + +### Example + +```go +package main + +import ( + "context" + "fmt" + "os" + openapiclient "./openapi" +) + +func main() { + id := "id_example" // string | ID is the session's ID. + + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + resp, r, err := apiClient.V0alpha2Api.RevokeSession(context.Background(), id).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `V0alpha2Api.RevokeSession``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } +} +``` + +### Path Parameters + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- +**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. +**id** | **string** | ID is the session's ID. | + +### Other Parameters + +Other parameters are passed through a pointer to a apiRevokeSessionRequest struct via the builder pattern + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + + +### Return type + + (empty response body) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + + +## RevokeSessions + +> RevokedSessions RevokeSessions(ctx).XSessionToken(xSessionToken).Cookie(cookie).Execute() + +Calling this endpoint invalidates all except the current session that belong to the logged-in user. Session data are not deleted. + + + +### Example + +```go +package main + +import ( + "context" + "fmt" + "os" + openapiclient "./openapi" +) + +func main() { + xSessionToken := "xSessionToken_example" // string | Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. (optional) + cookie := "cookie_example" // string | Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that scenario you must include the HTTP Cookie Header which originally was included in the request to your server. An example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`. It is ok if more than one cookie are included here as all other cookies will be ignored. (optional) + + configuration := openapiclient.NewConfiguration() + apiClient := openapiclient.NewAPIClient(configuration) + resp, r, err := apiClient.V0alpha2Api.RevokeSessions(context.Background()).XSessionToken(xSessionToken).Cookie(cookie).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "Error when calling `V0alpha2Api.RevokeSessions``: %v\n", err) + fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r) + } + // response from `RevokeSessions`: RevokedSessions + fmt.Fprintf(os.Stdout, "Response from `V0alpha2Api.RevokeSessions`: %v\n", resp) +} +``` + +### Path Parameters + + + +### Other Parameters + +Other parameters are passed through a pointer to a apiRevokeSessionsRequest struct via the builder pattern + + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **xSessionToken** | **string** | Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. | + **cookie** | **string** | Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that scenario you must include the HTTP Cookie Header which originally was included in the request to your server. An example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`. It is ok if more than one cookie are included here as all other cookies will be ignored. | + +### Return type + +[**RevokedSessions**](RevokedSessions.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) +[[Back to Model list]](../README.md#documentation-for-models) +[[Back to README]](../README.md) + + ## SubmitSelfServiceLoginFlow > SuccessfulSelfServiceLoginWithoutBrowser SubmitSelfServiceLoginFlow(ctx).Flow(flow).XSessionToken(xSessionToken).SubmitSelfServiceLoginFlowBody(submitSelfServiceLoginFlowBody).Execute() diff --git a/internal/httpclient/model_pagination.go b/internal/httpclient/model_pagination.go new file mode 100644 index 000000000000..cba8c9778e8d --- /dev/null +++ b/internal/httpclient/model_pagination.go @@ -0,0 +1,160 @@ +/* + * Ory Kratos API + * + * Documentation for all public and administrative Ory Kratos APIs. Public and administrative APIs are exposed on different ports. Public APIs can face the public internet without any protection while administrative APIs should never be exposed without prior authorization. To protect the administative API port you should use something like Nginx, Ory Oathkeeper, or any other technology capable of authorizing incoming requests. + * + * API version: 1.0.0 + * Contact: hi@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// Pagination struct for Pagination +type Pagination struct { + // Pagination Page + Page *int64 `json:"page,omitempty"` + // Items per Page This is the number of items per page. + PerPage *int64 `json:"per_page,omitempty"` +} + +// NewPagination instantiates a new Pagination object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewPagination() *Pagination { + this := Pagination{} + var page int64 = 0 + this.Page = &page + var perPage int64 = 250 + this.PerPage = &perPage + return &this +} + +// NewPaginationWithDefaults instantiates a new Pagination object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewPaginationWithDefaults() *Pagination { + this := Pagination{} + var page int64 = 0 + this.Page = &page + var perPage int64 = 250 + this.PerPage = &perPage + return &this +} + +// GetPage returns the Page field value if set, zero value otherwise. +func (o *Pagination) GetPage() int64 { + if o == nil || o.Page == nil { + var ret int64 + return ret + } + return *o.Page +} + +// GetPageOk returns a tuple with the Page field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Pagination) GetPageOk() (*int64, bool) { + if o == nil || o.Page == nil { + return nil, false + } + return o.Page, true +} + +// HasPage returns a boolean if a field has been set. +func (o *Pagination) HasPage() bool { + if o != nil && o.Page != nil { + return true + } + + return false +} + +// SetPage gets a reference to the given int64 and assigns it to the Page field. +func (o *Pagination) SetPage(v int64) { + o.Page = &v +} + +// GetPerPage returns the PerPage field value if set, zero value otherwise. +func (o *Pagination) GetPerPage() int64 { + if o == nil || o.PerPage == nil { + var ret int64 + return ret + } + return *o.PerPage +} + +// GetPerPageOk returns a tuple with the PerPage field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Pagination) GetPerPageOk() (*int64, bool) { + if o == nil || o.PerPage == nil { + return nil, false + } + return o.PerPage, true +} + +// HasPerPage returns a boolean if a field has been set. +func (o *Pagination) HasPerPage() bool { + if o != nil && o.PerPage != nil { + return true + } + + return false +} + +// SetPerPage gets a reference to the given int64 and assigns it to the PerPage field. +func (o *Pagination) SetPerPage(v int64) { + o.PerPage = &v +} + +func (o Pagination) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Page != nil { + toSerialize["page"] = o.Page + } + if o.PerPage != nil { + toSerialize["per_page"] = o.PerPage + } + return json.Marshal(toSerialize) +} + +type NullablePagination struct { + value *Pagination + isSet bool +} + +func (v NullablePagination) Get() *Pagination { + return v.value +} + +func (v *NullablePagination) Set(val *Pagination) { + v.value = val + v.isSet = true +} + +func (v NullablePagination) IsSet() bool { + return v.isSet +} + +func (v *NullablePagination) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullablePagination(val *Pagination) *NullablePagination { + return &NullablePagination{value: val, isSet: true} +} + +func (v NullablePagination) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullablePagination) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/model_revoked_sessions.go b/internal/httpclient/model_revoked_sessions.go new file mode 100644 index 000000000000..b0aac6ad5218 --- /dev/null +++ b/internal/httpclient/model_revoked_sessions.go @@ -0,0 +1,115 @@ +/* + * Ory Kratos API + * + * Documentation for all public and administrative Ory Kratos APIs. Public and administrative APIs are exposed on different ports. Public APIs can face the public internet without any protection while administrative APIs should never be exposed without prior authorization. To protect the administative API port you should use something like Nginx, Ory Oathkeeper, or any other technology capable of authorizing incoming requests. + * + * API version: 1.0.0 + * Contact: hi@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" +) + +// RevokedSessions struct for RevokedSessions +type RevokedSessions struct { + // The number of sessions that were revoked. + Count *int64 `json:"count,omitempty"` +} + +// NewRevokedSessions instantiates a new RevokedSessions object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewRevokedSessions() *RevokedSessions { + this := RevokedSessions{} + return &this +} + +// NewRevokedSessionsWithDefaults instantiates a new RevokedSessions object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewRevokedSessionsWithDefaults() *RevokedSessions { + this := RevokedSessions{} + return &this +} + +// GetCount returns the Count field value if set, zero value otherwise. +func (o *RevokedSessions) GetCount() int64 { + if o == nil || o.Count == nil { + var ret int64 + return ret + } + return *o.Count +} + +// GetCountOk returns a tuple with the Count field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *RevokedSessions) GetCountOk() (*int64, bool) { + if o == nil || o.Count == nil { + return nil, false + } + return o.Count, true +} + +// HasCount returns a boolean if a field has been set. +func (o *RevokedSessions) HasCount() bool { + if o != nil && o.Count != nil { + return true + } + + return false +} + +// SetCount gets a reference to the given int64 and assigns it to the Count field. +func (o *RevokedSessions) SetCount(v int64) { + o.Count = &v +} + +func (o RevokedSessions) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.Count != nil { + toSerialize["count"] = o.Count + } + return json.Marshal(toSerialize) +} + +type NullableRevokedSessions struct { + value *RevokedSessions + isSet bool +} + +func (v NullableRevokedSessions) Get() *RevokedSessions { + return v.value +} + +func (v *NullableRevokedSessions) Set(val *RevokedSessions) { + v.value = val + v.isSet = true +} + +func (v NullableRevokedSessions) IsSet() bool { + return v.isSet +} + +func (v *NullableRevokedSessions) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableRevokedSessions(val *RevokedSessions) *NullableRevokedSessions { + return &NullableRevokedSessions{value: val, isSet: true} +} + +func (v NullableRevokedSessions) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableRevokedSessions) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/persistence/sql/persister.go b/persistence/sql/persister.go index 2947dfa41ab2..d2c42897f94c 100644 --- a/persistence/sql/persister.go +++ b/persistence/sql/persister.go @@ -9,8 +9,8 @@ import ( "github.com/ory/kratos/corp" - "github.com/gobuffalo/pop/v5/columns" "github.com/gobuffalo/pop/v6" + "github.com/gobuffalo/pop/v6/columns" "github.com/gofrs/uuid" "github.com/pkg/errors" diff --git a/persistence/sql/persister_session.go b/persistence/sql/persister_session.go index 123fdfb1ae1d..27243c19acfb 100644 --- a/persistence/sql/persister_session.go +++ b/persistence/sql/persister_session.go @@ -5,6 +5,8 @@ import ( "database/sql" "fmt" + "github.com/gobuffalo/pop/v6" + "github.com/pkg/errors" "github.com/ory/kratos/corp" @@ -36,6 +38,39 @@ func (p *Persister) GetSession(ctx context.Context, sid uuid.UUID) (*session.Ses return &s, nil } +// ListSessionsByIdentity retrieves sessions for an identity from the store. +func (p *Persister) ListSessionsByIdentity(ctx context.Context, iID uuid.UUID, active *bool, page, perPage int, except uuid.UUID) ([]*session.Session, error) { + var s []*session.Session + nid := corp.ContextualizeNID(ctx, p.nid) + + if err := p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { + q := c.Where("identity_id = ? AND nid = ?", iID, nid).Paginate(page, perPage) + if except != uuid.Nil { + q = q.Where("id != ?", except) + } + if active != nil { + q = q.Where("active = ?", *active) + } + if err := q.All(&s); err != nil { + return sqlcon.HandleError(err) + } + + for _, s := range s { + i, err := p.GetIdentity(ctx, s.IdentityID) + if err != nil { + return err + } + + s.Identity = i + } + return nil + }); err != nil { + return nil, err + } + + return s, nil +} + func (p *Persister) UpsertSession(ctx context.Context, s *session.Session) error { s.NID = corp.ContextualizeNID(ctx, p.nid) @@ -126,3 +161,38 @@ func (p *Persister) RevokeSessionByToken(ctx context.Context, token string) erro } return nil } + +// RevokeSession revokes a given session. If the session does not exist or was not modified, +// it effectively has been revoked already, and therefore that case does not return an error. +func (p *Persister) RevokeSession(ctx context.Context, iID, sID uuid.UUID) error { + // #nosec G201 + err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( + "UPDATE %s SET active = false WHERE id = ? AND identity_id = ? AND nid = ?", + corp.ContextualizeTableName(ctx, "sessions"), + ), + sID, + iID, + corp.ContextualizeNID(ctx, p.nid), + ).Exec() + if err != nil { + return sqlcon.HandleError(err) + } + return nil +} + +// RevokeSessionsIdentityExcept marks all except the given session of an identity inactive. +func (p *Persister) RevokeSessionsIdentityExcept(ctx context.Context, iID, sID uuid.UUID) (int, error) { + // #nosec G201 + count, err := p.GetConnection(ctx).RawQuery(fmt.Sprintf( + "UPDATE %s SET active = false WHERE identity_id = ? AND id != ? AND nid = ?", + corp.ContextualizeTableName(ctx, "sessions"), + ), + iID, + sID, + corp.ContextualizeNID(ctx, p.nid), + ).ExecWithCount() + if err != nil { + return 0, sqlcon.HandleError(err) + } + return count, nil +} diff --git a/schema/handler.go b/schema/handler.go index 0ca0b479e22c..75277b5ffffb 100644 --- a/schema/handler.go +++ b/schema/handler.go @@ -119,24 +119,7 @@ type identitySchema struct { // nolint:deadcode,unused // swagger:parameters listIdentitySchemas type listIdentitySchemas struct { - // Items per Page - // - // This is the number of items per page. - // - // required: false - // in: query - // default: 100 - // min: 1 - // max: 500 - PerPage int `json:"per_page"` - - // Pagination Page - // - // required: false - // in: query - // default: 0 - // min: 0 - Page int `json:"page"` + x.PaginationParams } // swagger:route GET /schemas v0alpha2 listIdentitySchemas diff --git a/session/handler.go b/session/handler.go index 251140d86800..e678be76cc58 100644 --- a/session/handler.go +++ b/session/handler.go @@ -2,6 +2,9 @@ package session import ( "net/http" + "strconv" + + "github.com/ory/x/pointerx" "github.com/gofrs/uuid" "github.com/julienschmidt/httprouter" @@ -43,37 +46,48 @@ func NewHandler( } const ( - RouteCollection = "/sessions" - RouteWhoami = RouteCollection + "/whoami" - RouteIdentity = "/identities" - RouteDeleteSession = RouteIdentity + "/:id/sessions" + RouteCollection = "/sessions" + RouteWhoami = RouteCollection + "/whoami" + RouteSession = RouteCollection + "/:id" + RouteIdentity = "/identities" + RouteIdentitiesSessions = RouteIdentity + "/:id/sessions" ) func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) { - for _, m := range []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, - http.MethodDelete} { + for _, m := range []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch} { // Redirect to public endpoint admin.Handle(m, RouteWhoami, x.RedirectToPublicRoute(h.r)) } - admin.DELETE(RouteDeleteSession, h.deleteIdentitySessions) + admin.DELETE(RouteCollection, x.RedirectToPublicRoute(h.r)) + admin.DELETE(RouteSession, x.RedirectToPublicRoute(h.r)) + admin.GET(RouteCollection, x.RedirectToPublicRoute(h.r)) + + admin.GET(RouteIdentitiesSessions, h.adminListIdentitySessions) + admin.DELETE(RouteIdentitiesSessions, h.adminDeleteIdentitySessions) } func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) { // We need to completely ignore the whoami/logout path so that we do not accidentally set // some cookie. h.r.CSRFHandler().IgnorePath(RouteWhoami) + h.r.CSRFHandler().IgnorePath(RouteCollection) + h.r.CSRFHandler().IgnoreGlob(RouteCollection + "/*") h.r.CSRFHandler().IgnoreGlob(RouteIdentity + "/*/sessions") - for _, m := range []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, - http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} { + for _, m := range []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodConnect, http.MethodOptions, http.MethodTrace} { public.Handle(m, RouteWhoami, h.whoami) } - public.DELETE(RouteDeleteSession, x.RedirectToAdminRoute(h.r)) + + public.DELETE(RouteCollection, h.revokeSessions) + public.DELETE(RouteSession, h.revokeSession) + public.GET(RouteCollection, h.listSessions) + + public.DELETE(RouteIdentitiesSessions, x.RedirectToAdminRoute(h.r)) } // nolint:deadcode,unused -// swagger:parameters toSession +// swagger:parameters toSession revokeSessions listSessions type toSession struct { // Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`. // @@ -210,7 +224,7 @@ type adminDeleteIdentitySessions struct { // 401: jsonError // 404: jsonError // 500: jsonError -func (h *Handler) deleteIdentitySessions(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { +func (h *Handler) adminDeleteIdentitySessions(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { iID, err := uuid.FromString(ps.ByName("id")) if err != nil { h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()).WithDebug("could not parse UUID")) @@ -224,6 +238,211 @@ func (h *Handler) deleteIdentitySessions(w http.ResponseWriter, r *http.Request, w.WriteHeader(http.StatusNoContent) } +// swagger:parameters adminListIdentitySessions +// nolint:deadcode,unused +type adminListIdentitySessions struct { + // Active is a boolean flag that filters out sessions based on the state. If no value is provided, all sessions are returned. + // + // required: false + // in: query + Active bool `json:"active"` + + adminDeleteIdentitySessions + x.PaginationParams +} + +// swagger:route GET /identities/{id}/sessions v0alpha2 adminListIdentitySessions +// +// This endpoint returns all sessions that belong to the given Identity. +// +// This endpoint is useful for: +// +// - Listing all sessions that belong to an Identity in an administrative context. +// +// Schemes: http, https +// +// Security: +// oryAccessToken: +// +// Responses: +// 200: sessionList +// 400: jsonError +// 401: jsonError +// 404: jsonError +// 500: jsonError +func (h *Handler) adminListIdentitySessions(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + iID, err := uuid.FromString(ps.ByName("id")) + if err != nil { + h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()).WithDebug("could not parse UUID")) + return + } + + activeRaw := r.URL.Query().Get("active") + activeBool, err := strconv.ParseBool(activeRaw) + if activeRaw != "" && err != nil { + h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError("could not parse parameter active")) + return + } + + var active *bool + if activeRaw != "" { + active = &activeBool + } + + page, perPage := x.ParsePagination(r) + sess, err := h.r.SessionPersister().ListSessionsByIdentity(r.Context(), iID, active, page, perPage, uuid.Nil) + if err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + + h.r.Writer().Write(w, r, sess) +} + +// swagger:model revokedSessions +type revokeSessions struct { + // The number of sessions that were revoked. + Count int `json:"count"` +} + +// swagger:route DELETE /sessions v0alpha2 revokeSessions +// +// Calling this endpoint invalidates all except the current session that belong to the logged-in user. +// Session data are not deleted. +// +// This endpoint is useful for: +// +// - To forcefully logout the current user from all other devices and sessions +// +// Schemes: http, https +// +// Responses: +// 200: revokedSessions +// 400: jsonError +// 401: jsonError +// 404: jsonError +// 500: jsonError +func (h *Handler) revokeSessions(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + s, err := h.r.SessionManager().FetchFromRequest(r.Context(), r) + if err != nil { + h.r.Audit().WithRequest(r).WithError(err).Info("No valid session cookie found.") + h.r.Writer().WriteError(w, r, herodot.ErrUnauthorized.WithWrap(err).WithReasonf("No valid session cookie found.")) + return + } + + n, err := h.r.SessionPersister().RevokeSessionsIdentityExcept(r.Context(), s.IdentityID, s.ID) + if err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + + h.r.Writer().WriteCode(w, r, http.StatusOK, &revokeSessions{Count: n}) +} + +// swagger:parameters revokeSession +// nolint:deadcode,unused +type revokeSession struct { + // ID is the session's ID. + // + // required: true + // in: path + ID string `json:"id"` +} + +// swagger:route DELETE /sessions/{id} v0alpha2 revokeSession +// +// Calling this endpoint invalidates the specified session. The current session cannot be revoked. +// Session data are not deleted. +// +// This endpoint is useful for: +// +// - To forcefully logout the current user from another device or session +// +// Schemes: http, https +// +// Responses: +// 204: emptyResponse +// 400: jsonError +// 401: jsonError +// 500: jsonError +func (h *Handler) revokeSession(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + sid := ps.ByName("id") + if sid == "whoami" { + // Special case where we actually want to handle the whomai endpoint. + h.whoami(w, r, ps) + return + } + + s, err := h.r.SessionManager().FetchFromRequest(r.Context(), r) + if err != nil { + h.r.Audit().WithRequest(r).WithError(err).Info("No valid session cookie found.") + h.r.Writer().WriteError(w, r, herodot.ErrUnauthorized.WithWrap(err).WithReasonf("No valid session cookie found.")) + return + } + + sessionID, err := uuid.FromString(sid) + if err != nil { + h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()).WithDebug("could not parse UUID")) + return + } + if sessionID == s.ID { + h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError("cannot revoke current session").WithDebug("use the logout flow instead")) + return + } + + if err := h.r.SessionPersister().RevokeSession(r.Context(), s.Identity.ID, sessionID); err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + + h.r.Writer().WriteCode(w, r, http.StatusNoContent, nil) +} + +// swagger:parameters listSessions +// nolint:deadcode,unused +type listSessions struct { + x.PaginationParams +} + +// swagger:model sessionList +// nolint:deadcode,unused +type sessionList []*Session + +// swagger:route GET /sessions v0alpha2 listSessions +// +// This endpoints returns all other active sessions that belong to the logged-in user. +// The current session can be retrieved by calling the `/sessions/whoami` endpoint. +// +// This endpoint is useful for: +// +// - Displaying all other sessions that belong to the logged-in user +// +// Schemes: http, https +// +// Responses: +// 200: sessionList +// 400: jsonError +// 401: jsonError +// 404: jsonError +// 500: jsonError +func (h *Handler) listSessions(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + s, err := h.r.SessionManager().FetchFromRequest(r.Context(), r) + if err != nil { + h.r.Audit().WithRequest(r).WithError(err).Info("No valid session cookie found.") + h.r.Writer().WriteError(w, r, herodot.ErrUnauthorized.WithWrap(err).WithReasonf("No valid session cookie found.")) + return + } + + page, perPage := x.ParsePagination(r) + sess, err := h.r.SessionPersister().ListSessionsByIdentity(r.Context(), s.IdentityID, pointerx.Bool(true), page, perPage, s.ID) + if err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + + h.r.Writer().Write(w, r, sess) +} + func (h *Handler) IsAuthenticated(wrap httprouter.Handle, onUnauthenticated httprouter.Handle) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { if _, err := h.r.SessionManager().FetchFromRequest(r.Context(), r); err != nil { diff --git a/session/handler_test.go b/session/handler_test.go index 534d1c51e730..3efd492c50ee 100644 --- a/session/handler_test.go +++ b/session/handler_test.go @@ -2,12 +2,16 @@ package session_test import ( "context" + "encoding/json" "fmt" + "io/ioutil" "net/http" "net/http/httptest" "testing" "time" + "github.com/bxcodec/faker/v3" + "github.com/tidwall/gjson" "github.com/ory/kratos/identity" @@ -357,7 +361,8 @@ func TestIsAuthenticated(t *testing.T) { } } -func TestHandlerDeleteSessionByIdentityID(t *testing.T) { +func TestHandlerAdminSessionManagement(t *testing.T) { + ctx := context.Background() conf, reg := internal.NewFastRegistryWithMocks(t) _, ts, _, _ := testhelpers.NewKratosServerWithCSRFAndRouters(t, reg) @@ -369,28 +374,56 @@ func TestHandlerDeleteSessionByIdentityID(t *testing.T) { t.Run("case=should return 202 after invalidating all sessions", func(t *testing.T) { client := testhelpers.NewClientWithCookies(t) i := identity.NewIdentity("") - require.NoError(t, reg.IdentityManager().Create(context.Background(), i)) + require.NoError(t, reg.IdentityManager().Create(ctx, i)) s := &Session{Identity: i} - require.NoError(t, reg.SessionPersister().UpsertSession(context.Background(), s)) + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, s)) + + t.Run("should list session", func(t *testing.T) { + req, _ := http.NewRequest("GET", ts.URL+"/identities/"+i.ID.String()+"/sessions", nil) + res, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + + var sessions []Session + require.NoError(t, json.NewDecoder(res.Body).Decode(&sessions)) + require.Len(t, sessions, 1) + assert.Equal(t, s.ID, sessions[0].ID) + }) req, _ := http.NewRequest("DELETE", ts.URL+"/identities/"+i.ID.String()+"/sessions", nil) res, err := client.Do(req) require.NoError(t, err) require.Equal(t, http.StatusNoContent, res.StatusCode) - _, err = reg.SessionPersister().GetSession(context.Background(), s.ID) + _, err = reg.SessionPersister().GetSession(ctx, s.ID) require.True(t, errors.Is(err, sqlcon.ErrNoRows)) + + t.Run("should not list session", func(t *testing.T) { + req, _ := http.NewRequest("GET", ts.URL+"/identities/"+i.ID.String()+"/sessions", nil) + res, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, http.StatusOK, res.StatusCode) + + var sessions []Session + require.NoError(t, json.NewDecoder(res.Body).Decode(&sessions)) + assert.Len(t, sessions, 0) + }) }) t.Run("case=should return 400 when bad UUID is sent", func(t *testing.T) { client := testhelpers.NewClientWithCookies(t) - req, _ := http.NewRequest("DELETE", ts.URL+"/identities/BADUUID/sessions", nil) - res, err := client.Do(req) - require.NoError(t, err) - require.Equal(t, http.StatusBadRequest, res.StatusCode) + + for _, method := range []string{http.MethodGet, http.MethodDelete} { + t.Run("http method="+method, func(t *testing.T) { + req, _ := http.NewRequest(method, ts.URL+"/identities/BADUUID/sessions", nil) + res, err := client.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusBadRequest, res.StatusCode) + }) + } }) - t.Run("case=should return 404 when calling with missing UUID", func(t *testing.T) { + t.Run("case=should return 404 when deleting with unknown UUID", func(t *testing.T) { client := testhelpers.NewClientWithCookies(t) someID, _ := uuid.NewV4() req, _ := http.NewRequest("DELETE", ts.URL+"/identities/"+someID.String()+"/sessions", nil) @@ -398,4 +431,172 @@ func TestHandlerDeleteSessionByIdentityID(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusNotFound, res.StatusCode) }) + + t.Run("case=should respect active on list", func(t *testing.T) { + client := testhelpers.NewClientWithCookies(t) + i := identity.NewIdentity("") + require.NoError(t, reg.IdentityManager().Create(ctx, i)) + + sess := make([]Session, 2) + for j := range sess { + require.NoError(t, faker.FakeData(&sess[j])) + sess[j].Identity = i + sess[j].Active = j%2 == 0 + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, &sess[j])) + } + + for _, tc := range []struct { + activeOnly string + expectedIDs []uuid.UUID + }{ + { + activeOnly: "true", + expectedIDs: []uuid.UUID{sess[0].ID}, + }, + { + activeOnly: "false", + expectedIDs: []uuid.UUID{sess[1].ID}, + }, + { + activeOnly: "", + expectedIDs: []uuid.UUID{sess[0].ID, sess[1].ID}, + }, + } { + t.Run(fmt.Sprintf("active=%#v", tc.activeOnly), func(t *testing.T) { + reqURL := ts.URL + "/identities/" + i.ID.String() + "/sessions" + if tc.activeOnly != "" { + reqURL += "?active=" + tc.activeOnly + } + req, _ := http.NewRequest("GET", reqURL, nil) + res, err := client.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode) + + var sessions []Session + require.NoError(t, json.NewDecoder(res.Body).Decode(&sessions)) + require.Equal(t, len(sessions), len(tc.expectedIDs)) + + for _, id := range tc.expectedIDs { + found := false + for _, s := range sessions { + found = found || s.ID == id + } + assert.True(t, found) + } + }) + } + }) +} + +func TestHandlerSelfServiceSessionManagement(t *testing.T) { + ctx := context.Background() + conf, reg := internal.NewFastRegistryWithMocks(t) + ts, _, r, _ := testhelpers.NewKratosServerWithCSRFAndRouters(t, reg) + + // set this intermediate because kratos needs some valid url for CRUDE operations + conf.MustSet(config.ViperKeyPublicBaseURL, "http://example.com") + testhelpers.SetDefaultIdentitySchema(t, conf, "file://./stub/identity.schema.json") + conf.MustSet(config.ViperKeyPublicBaseURL, ts.URL) + + var setup func(t *testing.T) (*http.Client, *identity.Identity, *Session) + { + // we limit the scope of the channels, so you cannot accidentally mess up a test case + ident := make(chan *identity.Identity, 1) + sess := make(chan *Session, 1) + r.GET("/set", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + h, s := testhelpers.MockSessionCreateHandlerWithIdentity(t, reg, <-ident) + h(w, r, ps) + sess <- s + }) + + setup = func(t *testing.T) (*http.Client, *identity.Identity, *Session) { + client := testhelpers.NewClientWithCookies(t) + i := identity.NewIdentity("") // the identity is created by the handler + + ident <- i + testhelpers.MockHydrateCookieClient(t, client, ts.URL+"/set") + return client, i, <-sess + } + } + + t.Run("case=should return 200 and number after invalidating all other sessions", func(t *testing.T) { + client, i, currSess := setup(t) + + otherSess := Session{} + require.NoError(t, faker.FakeData(&otherSess)) + otherSess.Identity = i + otherSess.Active = true + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, &otherSess)) + + req, _ := http.NewRequest("DELETE", ts.URL+"/sessions", nil) + res, err := client.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusOK, res.StatusCode) + body, err := ioutil.ReadAll(res.Body) + require.NoError(t, err) + assert.Equal(t, int64(1), gjson.GetBytes(body, "count").Int(), "%s", body) + + actualOther, err := reg.SessionPersister().GetSession(ctx, otherSess.ID) + require.NoError(t, err) + assert.False(t, actualOther.Active) + + actualCurr, err := reg.SessionPersister().GetSession(ctx, currSess.ID) + require.NoError(t, err) + assert.True(t, actualCurr.Active) + }) + + t.Run("case=should revoke specific other session", func(t *testing.T) { + client, i, _ := setup(t) + + others := make([]Session, 2) + for j := range others { + require.NoError(t, faker.FakeData(&others[j])) + others[j].Identity = i + others[j].Active = true + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, &others[j])) + } + + req, _ := http.NewRequest("DELETE", ts.URL+"/sessions/"+others[0].ID.String(), nil) + res, err := client.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusNoContent, res.StatusCode) + + actualOthers, err := reg.SessionPersister().ListSessionsByIdentity(ctx, i.ID, nil, 1, 10, uuid.Nil) + require.NoError(t, err) + require.Len(t, actualOthers, 3) + + for _, s := range actualOthers { + if s.ID == others[0].ID { + assert.False(t, s.Active) + } else { + assert.True(t, s.Active) + } + } + }) + + t.Run("case=should not revoke current session", func(t *testing.T) { + client, _, currSess := setup(t) + + req, _ := http.NewRequest("DELETE", ts.URL+"/sessions/"+currSess.ID.String(), nil) + resp, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, http.StatusBadRequest, resp.StatusCode) + }) + + t.Run("case=should not error on unknown or revoked session", func(t *testing.T) { + client, i, _ := setup(t) + + otherSess := Session{} + require.NoError(t, faker.FakeData(&otherSess)) + otherSess.Identity = i + otherSess.Active = false + require.NoError(t, reg.SessionPersister().UpsertSession(ctx, &otherSess)) + + for j, id := range []uuid.UUID{otherSess.ID, uuid.Must(uuid.NewV4())} { + req, _ := http.NewRequest("DELETE", ts.URL+"/sessions/"+id.String(), nil) + resp, err := client.Do(req) + require.NoError(t, err) + assert.Equal(t, http.StatusNoContent, resp.StatusCode, "case=%d", j) + } + }) } diff --git a/session/persistence.go b/session/persistence.go index 752629c17b4e..a2e1eaa6686d 100644 --- a/session/persistence.go +++ b/session/persistence.go @@ -22,6 +22,9 @@ type Persister interface { // GetSession retrieves a session from the store. GetSession(ctx context.Context, sid uuid.UUID) (*Session, error) + // ListSessionsByIdentity retrieves sessions for an identity from the store. + ListSessionsByIdentity(ctx context.Context, iID uuid.UUID, active *bool, page, perPage int, except uuid.UUID) ([]*Session, error) + // UpsertSession inserts or updates a session into / in the store. UpsertSession(ctx context.Context, s *Session) error @@ -45,6 +48,12 @@ type Persister interface { // RevokeSessionByToken marks a session inactive with the given token. RevokeSessionByToken(ctx context.Context, token string) error + + // RevokeSession marks a given session inactive. + RevokeSession(ctx context.Context, iID, sID uuid.UUID) error + + // RevokeSessionsIdentityExcept marks all except the given session of an identity inactive. It returns the number of sessions that were revoked. + RevokeSessionsIdentityExcept(ctx context.Context, iID, sID uuid.UUID) (int, error) } func TestPersister(ctx context.Context, conf *config.Config, p interface { diff --git a/session/test/persistence.go b/session/test/persistence.go index 15dd7438dfae..a0a5296397a3 100644 --- a/session/test/persistence.go +++ b/session/test/persistence.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/ory/x/pointerx" + "github.com/ory/kratos/identity" "github.com/bxcodec/faker/v3" @@ -83,6 +85,94 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { }) }) + t.Run("method=list by identity", func(t *testing.T) { + i := identity.NewIdentity("") + require.NoError(t, p.CreateIdentity(ctx, i)) + sess := make([]session.Session, 4) + for j := range sess { + require.NoError(t, faker.FakeData(&sess[j])) + sess[j].Identity = i + sess[j].Active = j%2 == 0 + require.NoError(t, p.UpsertSession(ctx, &sess[j])) + } + + for _, tc := range []struct { + desc string + except uuid.UUID + expected []session.Session + active *bool + }{ + { + desc: "all", + expected: sess, + }, + { + desc: "except one", + except: sess[0].ID, + expected: []session.Session{ + sess[1], + sess[2], + sess[3], + }, + }, + { + desc: "active only", + active: pointerx.Bool(true), + expected: []session.Session{ + sess[0], + sess[2], + }, + }, + { + desc: "active only and except", + active: pointerx.Bool(true), + except: sess[0].ID, + expected: []session.Session{ + sess[2], + }, + }, + { + desc: "inactive only", + active: pointerx.Bool(false), + expected: []session.Session{ + sess[1], + sess[3], + }, + }, + { + desc: "inactive only and except", + active: pointerx.Bool(false), + except: sess[3].ID, + expected: []session.Session{ + sess[1], + }, + }, + } { + t.Run("case="+tc.desc, func(t *testing.T) { + actual, err := p.ListSessionsByIdentity(ctx, i.ID, tc.active, 1, 10, tc.except) + require.NoError(t, err) + + require.Equal(t, len(tc.expected), len(actual)) + for _, es := range tc.expected { + found := false + for _, as := range actual { + if as.ID == es.ID { + found = true + } + } + assert.True(t, found) + } + }) + } + + t.Run("other network", func(t *testing.T) { + _, other := testhelpers.NewNetwork(t, ctx, p) + actual, err := other.ListSessionsByIdentity(ctx, i.ID, nil, 1, 10, uuid.Nil) + require.NoError(t, err) + assert.Len(t, actual, 0) + }) + }) + t.Run("case=update session", func(t *testing.T) { expected.AuthenticatorAssuranceLevel = identity.AuthenticatorAssuranceLevel3 require.NoError(t, p.UpsertSession(ctx, &expected)) @@ -170,6 +260,99 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { assert.False(t, actual.Active) }) + t.Run("method=revoke other sessions for identity", func(t *testing.T) { + // here we set up 2 identities with each having 2 sessions + sessions := make([]session.Session, 4) + for i := range sessions { + require.NoError(t, faker.FakeData(&sessions[i])) + } + require.NoError(t, p.CreateIdentity(ctx, sessions[0].Identity)) + require.NoError(t, p.CreateIdentity(ctx, sessions[2].Identity)) + sessions[1].IdentityID, sessions[1].Identity = sessions[0].IdentityID, sessions[0].Identity + sessions[3].IdentityID, sessions[3].Identity = sessions[2].IdentityID, sessions[2].Identity + for i := range sessions { + sessions[i].Active = true + require.NoError(t, p.UpsertSession(ctx, &sessions[i])) + } + + t.Run("on another network", func(t *testing.T) { + _, other := testhelpers.NewNetwork(t, ctx, p) + n, err := other.RevokeSessionsIdentityExcept(ctx, sessions[0].IdentityID, sessions[0].ID) + require.NoError(t, err) + assert.Equal(t, 0, n) + + for _, s := range sessions { + actual, err := p.GetSession(ctx, s.ID) + require.NoError(t, err) + assert.True(t, actual.Active) + } + }) + + n, err := p.RevokeSessionsIdentityExcept(ctx, sessions[0].IdentityID, sessions[0].ID) + require.NoError(t, err) + assert.Equal(t, 1, n) + + actual, err := p.ListSessionsByIdentity(ctx, sessions[0].IdentityID, nil, 1, 10, uuid.Nil) + require.NoError(t, err) + require.Len(t, actual, 2) + + if actual[0].ID == sessions[0].ID { + assert.True(t, actual[0].Active) + assert.False(t, actual[1].Active) + } else { + assert.Equal(t, actual[0].ID, sessions[1].ID) + assert.True(t, actual[1].Active) + assert.False(t, actual[0].Active) + } + + otherIdentitiesSessions, err := p.ListSessionsByIdentity(ctx, sessions[2].IdentityID, nil, 1, 10, uuid.Nil) + require.NoError(t, err) + require.Len(t, actual, 2) + + for _, s := range otherIdentitiesSessions { + assert.True(t, s.Active) + } + }) + + t.Run("method=revoke specific session for identity", func(t *testing.T) { + sessions := make([]session.Session, 2) + for i := range sessions { + require.NoError(t, faker.FakeData(&sessions[i])) + } + require.NoError(t, p.CreateIdentity(ctx, sessions[0].Identity)) + sessions[1].IdentityID, sessions[1].Identity = sessions[0].IdentityID, sessions[0].Identity + for i := range sessions { + sessions[i].Active = true + require.NoError(t, p.UpsertSession(ctx, &sessions[i])) + } + + t.Run("on another network", func(t *testing.T) { + _, other := testhelpers.NewNetwork(t, ctx, p) + require.NoError(t, other.RevokeSession(ctx, sessions[0].IdentityID, sessions[0].ID)) + + for _, s := range sessions { + actual, err := p.GetSession(ctx, s.ID) + require.NoError(t, err) + assert.True(t, actual.Active) + } + }) + + require.NoError(t, p.RevokeSession(ctx, sessions[0].IdentityID, sessions[0].ID)) + + actual, err := p.ListSessionsByIdentity(ctx, sessions[0].IdentityID, nil, 1, 10, uuid.Nil) + require.NoError(t, err) + require.Len(t, actual, 2) + + if actual[0].ID == sessions[0].ID { + assert.False(t, actual[0].Active) + assert.True(t, actual[1].Active) + } else { + assert.Equal(t, actual[0].ID, sessions[1].ID) + assert.False(t, actual[1].Active) + assert.True(t, actual[0].Active) + } + }) + t.Run("case=delete session for", func(t *testing.T) { var expected1 session.Session var expected2 session.Session diff --git a/spec/api.json b/spec/api.json index afee92988f97..894aaa787813 100755 --- a/spec/api.json +++ b/spec/api.json @@ -471,6 +471,36 @@ "title": "NullTime implements sql.NullTime functionality.", "type": "string" }, + "pagination": { + "properties": { + "page": { + "default": 0, + "description": "Pagination Page", + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "per_page": { + "default": 250, + "description": "Items per Page\n\nThis is the number of items per page.", + "format": "int64", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + "type": "object" + }, + "revokedSessions": { + "properties": { + "count": { + "description": "The number of sessions that were revoked.", + "format": "int64", + "type": "integer" + } + }, + "type": "object" + }, "selfServiceBrowserLocationChangeRequiredError": { "properties": { "code": { @@ -1005,6 +1035,12 @@ }, "type": "object" }, + "sessionList": { + "items": { + "$ref": "#/components/schemas/session" + }, + "type": "array" + }, "settingsProfileFormConfig": { "properties": { "action": { @@ -2063,9 +2099,9 @@ "in": "query", "name": "per_page", "schema": { - "default": 100, + "default": 250, "format": "int64", - "maximum": 500, + "maximum": 1000, "minimum": 1, "type": "integer" } @@ -2453,6 +2489,113 @@ "tags": [ "v0alpha2" ] + }, + "get": { + "description": "This endpoint is useful for:\n\nListing all sessions that belong to an Identity in an administrative context.", + "operationId": "adminListIdentitySessions", + "parameters": [ + { + "description": "ID is the identity's ID.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Items per Page\n\nThis is the number of items per page.", + "in": "query", + "name": "per_page", + "schema": { + "default": 250, + "format": "int64", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Pagination Page", + "in": "query", + "name": "page", + "schema": { + "default": 0, + "format": "int64", + "minimum": 0, + "type": "integer" + } + }, + { + "description": "Active is a boolean flag that filters out sessions based on the state. If no value is provided, all sessions are returned.", + "in": "query", + "name": "active", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sessionList" + } + } + }, + "description": "sessionList" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "security": [ + { + "oryAccessToken": [] + } + ], + "summary": "This endpoint returns all sessions that belong to the given Identity.", + "tags": [ + "v0alpha2" + ] } }, "/recovery/link": { @@ -2527,9 +2670,9 @@ "in": "query", "name": "per_page", "schema": { - "default": 100, + "default": 250, "format": "int64", - "maximum": 500, + "maximum": 1000, "minimum": 1, "type": "integer" } @@ -4214,6 +4357,187 @@ ] } }, + "/sessions": { + "delete": { + "description": "This endpoint is useful for:\n\nTo forcefully logout the current user from all other devices and sessions", + "operationId": "revokeSessions", + "parameters": [ + { + "description": "Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`.", + "in": "header", + "name": "X-Session-Token", + "schema": { + "type": "string" + } + }, + { + "description": "Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that\nscenario you must include the HTTP Cookie Header which originally was included in the request to your server.\nAn example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`.\n\nIt is ok if more than one cookie are included here as all other cookies will be ignored.", + "in": "header", + "name": "Cookie", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/revokedSessions" + } + } + }, + "description": "revokedSessions" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "Calling this endpoint invalidates all except the current session that belong to the logged-in user.\nSession data are not deleted.", + "tags": [ + "v0alpha2" + ] + }, + "get": { + "description": "This endpoint is useful for:\n\nDisplaying all other sessions that belong to the logged-in user", + "operationId": "listSessions", + "parameters": [ + { + "description": "Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`.", + "in": "header", + "name": "X-Session-Token", + "schema": { + "type": "string" + } + }, + { + "description": "Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that\nscenario you must include the HTTP Cookie Header which originally was included in the request to your server.\nAn example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`.\n\nIt is ok if more than one cookie are included here as all other cookies will be ignored.", + "in": "header", + "name": "Cookie", + "schema": { + "type": "string" + } + }, + { + "description": "Items per Page\n\nThis is the number of items per page.", + "in": "query", + "name": "per_page", + "schema": { + "default": 250, + "format": "int64", + "maximum": 1000, + "minimum": 1, + "type": "integer" + } + }, + { + "description": "Pagination Page", + "in": "query", + "name": "page", + "schema": { + "default": 0, + "format": "int64", + "minimum": 0, + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/sessionList" + } + } + }, + "description": "sessionList" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "This endpoints returns all other active sessions that belong to the logged-in user.\nThe current session can be retrieved by calling the `/sessions/whoami` endpoint.", + "tags": [ + "v0alpha2" + ] + } + }, "/sessions/whoami": { "get": { "description": "Uses the HTTP Headers in the GET request to determine (e.g. by using checking the cookies) who is authenticated.\nReturns a session object in the body or 401 if the credentials are invalid or no credentials were sent.\nAdditionally when the request it successful it adds the user ID to the 'X-Kratos-Authenticated-Identity-Id' header in the response.\n\nIf you call this endpoint from a server-side application, you must forward the HTTP Cookie Header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/protected-endpoint', async function (req, res) {\nconst session = await client.toSession(undefined, req.header('cookie'))\n\nconsole.log(session)\n})\n```\n\nWhen calling this endpoint from a non-browser application (e.g. mobile app) you must include the session token:\n\n```js\npseudo-code example\n...\nconst session = await client.toSession(\"the-session-token\")\n\nconsole.log(session)\n```\n\nDepending on your configuration this endpoint might return a 403 status code if the session has a lower Authenticator\nAssurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn\ncredentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user\nto sign in with the second factor or change the configuration.\n\nThis endpoint is useful for:\n\nAJAX calls. Remember to send credentials and set up CORS correctly!\nReverse proxies and API Gateways\nServer-side calls - use the `X-Session-Token` header!\n\nThis endpoint authenticates users by checking\n\nif the `Cookie` HTTP header was set containing an Ory Kratos Session Cookie;\nif the `Authorization: bearer \u003cory-session-token\u003e` HTTP header was set with a valid Ory Kratos Session Token;\nif the `X-Session-Token` HTTP header was set with a valid Ory Kratos Session Token.\n\nIf none of these headers are set or the cooke or token are invalid, the endpoint returns a HTTP 401 status code.\n\nAs explained above, this request may fail due to several reasons. The `error.id` can be one of:\n\n`session_inactive`: No active session was found in the request (e.g. no Ory Session Cookie / Ory Session Token).\n`session_aal2_required`: An active session was found but it does not fulfil the Authenticator Assurance Level, implying that the session must (e.g.) authenticate the second factor.", @@ -4286,6 +4610,62 @@ ] } }, + "/sessions/{id}": { + "delete": { + "description": "This endpoint is useful for:\n\nTo forcefully logout the current user from another device or session", + "operationId": "revokeSession", + "parameters": [ + { + "description": "ID is the session's ID.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "$ref": "#/components/responses/emptyResponse" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + }, + "500": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/jsonError" + } + } + }, + "description": "jsonError" + } + }, + "summary": "Calling this endpoint invalidates the specified session. The current session cannot be revoked.\nSession data are not deleted.", + "tags": [ + "v0alpha2" + ] + } + }, "/version": { "get": { "description": "This endpoint returns the version of Ory Kratos.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the version will never\nrefer to the cluster state, only to a single instance.", diff --git a/spec/swagger.json b/spec/swagger.json index d317c09cfb43..f52b0d92db11 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -119,11 +119,11 @@ "operationId": "adminListIdentities", "parameters": [ { - "maximum": 500, + "maximum": 1000, "minimum": 1, "type": "integer", "format": "int64", - "default": 100, + "default": 250, "description": "Items per Page\n\nThis is the number of items per page.", "name": "per_page", "in": "query" @@ -393,6 +393,89 @@ } }, "/identities/{id}/sessions": { + "get": { + "security": [ + { + "oryAccessToken": [] + } + ], + "description": "This endpoint is useful for:\n\nListing all sessions that belong to an Identity in an administrative context.", + "schemes": [ + "http", + "https" + ], + "tags": [ + "v0alpha2" + ], + "summary": "This endpoint returns all sessions that belong to the given Identity.", + "operationId": "adminListIdentitySessions", + "parameters": [ + { + "type": "string", + "description": "ID is the identity's ID.", + "name": "id", + "in": "path", + "required": true + }, + { + "maximum": 1000, + "minimum": 1, + "type": "integer", + "format": "int64", + "default": 250, + "description": "Items per Page\n\nThis is the number of items per page.", + "name": "per_page", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "format": "int64", + "default": 0, + "description": "Pagination Page", + "name": "page", + "in": "query" + }, + { + "type": "boolean", + "description": "Active is a boolean flag that filters out sessions based on the state. If no value is provided, all sessions are returned.", + "name": "active", + "in": "query" + } + ], + "responses": { + "200": { + "description": "sessionList", + "schema": { + "$ref": "#/definitions/sessionList" + } + }, + "400": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "401": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "404": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "500": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + } + } + }, "delete": { "security": [ { @@ -520,11 +603,11 @@ "operationId": "listIdentitySchemas", "parameters": [ { - "maximum": 500, + "maximum": 1000, "minimum": 1, "type": "integer", "format": "int64", - "default": 100, + "default": 250, "description": "Items per Page\n\nThis is the number of items per page.", "name": "per_page", "in": "query" @@ -1914,6 +1997,143 @@ } } }, + "/sessions": { + "get": { + "description": "This endpoint is useful for:\n\nDisplaying all other sessions that belong to the logged-in user", + "schemes": [ + "http", + "https" + ], + "tags": [ + "v0alpha2" + ], + "summary": "This endpoints returns all other active sessions that belong to the logged-in user.\nThe current session can be retrieved by calling the `/sessions/whoami` endpoint.", + "operationId": "listSessions", + "parameters": [ + { + "type": "string", + "description": "Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`.", + "name": "X-Session-Token", + "in": "header" + }, + { + "type": "string", + "description": "Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that\nscenario you must include the HTTP Cookie Header which originally was included in the request to your server.\nAn example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`.\n\nIt is ok if more than one cookie are included here as all other cookies will be ignored.", + "name": "Cookie", + "in": "header" + }, + { + "maximum": 1000, + "minimum": 1, + "type": "integer", + "format": "int64", + "default": 250, + "description": "Items per Page\n\nThis is the number of items per page.", + "name": "per_page", + "in": "query" + }, + { + "minimum": 0, + "type": "integer", + "format": "int64", + "default": 0, + "description": "Pagination Page", + "name": "page", + "in": "query" + } + ], + "responses": { + "200": { + "description": "sessionList", + "schema": { + "$ref": "#/definitions/sessionList" + } + }, + "400": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "401": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "404": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "500": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + } + } + }, + "delete": { + "description": "This endpoint is useful for:\n\nTo forcefully logout the current user from all other devices and sessions", + "schemes": [ + "http", + "https" + ], + "tags": [ + "v0alpha2" + ], + "summary": "Calling this endpoint invalidates all except the current session that belong to the logged-in user.\nSession data are not deleted.", + "operationId": "revokeSessions", + "parameters": [ + { + "type": "string", + "description": "Set the Session Token when calling from non-browser clients. A session token has a format of `MP2YWEMeM8MxjkGKpH4dqOQ4Q4DlSPaj`.", + "name": "X-Session-Token", + "in": "header" + }, + { + "type": "string", + "description": "Set the Cookie Header. This is especially useful when calling this endpoint from a server-side application. In that\nscenario you must include the HTTP Cookie Header which originally was included in the request to your server.\nAn example of a session in the HTTP Cookie Header is: `ory_kratos_session=a19iOVAbdzdgl70Rq1QZmrKmcjDtdsviCTZx7m9a9yHIUS8Wa9T7hvqyGTsLHi6Qifn2WUfpAKx9DWp0SJGleIn9vh2YF4A16id93kXFTgIgmwIOvbVAScyrx7yVl6bPZnCx27ec4WQDtaTewC1CpgudeDV2jQQnSaCP6ny3xa8qLH-QUgYqdQuoA_LF1phxgRCUfIrCLQOkolX5nv3ze_f==`.\n\nIt is ok if more than one cookie are included here as all other cookies will be ignored.", + "name": "Cookie", + "in": "header" + } + ], + "responses": { + "200": { + "description": "revokedSessions", + "schema": { + "$ref": "#/definitions/revokedSessions" + } + }, + "400": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "401": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "404": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "500": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + } + } + } + }, "/sessions/whoami": { "get": { "description": "Uses the HTTP Headers in the GET request to determine (e.g. by using checking the cookies) who is authenticated.\nReturns a session object in the body or 401 if the credentials are invalid or no credentials were sent.\nAdditionally when the request it successful it adds the user ID to the 'X-Kratos-Authenticated-Identity-Id' header in the response.\n\nIf you call this endpoint from a server-side application, you must forward the HTTP Cookie Header to this endpoint:\n\n```js\npseudo-code example\nrouter.get('/protected-endpoint', async function (req, res) {\nconst session = await client.toSession(undefined, req.header('cookie'))\n\nconsole.log(session)\n})\n```\n\nWhen calling this endpoint from a non-browser application (e.g. mobile app) you must include the session token:\n\n```js\npseudo-code example\n...\nconst session = await client.toSession(\"the-session-token\")\n\nconsole.log(session)\n```\n\nDepending on your configuration this endpoint might return a 403 status code if the session has a lower Authenticator\nAssurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn\ncredentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user\nto sign in with the second factor or change the configuration.\n\nThis endpoint is useful for:\n\nAJAX calls. Remember to send credentials and set up CORS correctly!\nReverse proxies and API Gateways\nServer-side calls - use the `X-Session-Token` header!\n\nThis endpoint authenticates users by checking\n\nif the `Cookie` HTTP header was set containing an Ory Kratos Session Cookie;\nif the `Authorization: bearer \u003cory-session-token\u003e` HTTP header was set with a valid Ory Kratos Session Token;\nif the `X-Session-Token` HTTP header was set with a valid Ory Kratos Session Token.\n\nIf none of these headers are set or the cooke or token are invalid, the endpoint returns a HTTP 401 status code.\n\nAs explained above, this request may fail due to several reasons. The `error.id` can be one of:\n\n`session_inactive`: No active session was found in the request (e.g. no Ory Session Cookie / Ory Session Token).\n`session_aal2_required`: An active session was found but it does not fulfil the Authenticator Assurance Level, implying that the session must (e.g.) authenticate the second factor.", @@ -1971,6 +2191,52 @@ } } }, + "/sessions/{id}": { + "delete": { + "description": "This endpoint is useful for:\n\nTo forcefully logout the current user from another device or session", + "schemes": [ + "http", + "https" + ], + "tags": [ + "v0alpha2" + ], + "summary": "Calling this endpoint invalidates the specified session. The current session cannot be revoked.\nSession data are not deleted.", + "operationId": "revokeSession", + "parameters": [ + { + "type": "string", + "description": "ID is the session's ID.", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/emptyResponse" + }, + "400": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "401": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + }, + "500": { + "description": "jsonError", + "schema": { + "$ref": "#/definitions/jsonError" + } + } + } + } + }, "/version": { "get": { "description": "This endpoint returns the service version typically notated using semantic versioning.\n\nIf the service supports TLS Edge Termination, this endpoint does not require the\n`X-Forwarded-Proto` header to be set.\n\nBe aware that if you are running multiple nodes of this service, the health status will never\nrefer to the cluster state, only to a single instance.", @@ -2442,6 +2708,36 @@ "format": "date-time", "title": "NullTime implements sql.NullTime functionality." }, + "pagination": { + "type": "object", + "properties": { + "page": { + "description": "Pagination Page", + "type": "integer", + "format": "int64", + "default": 0, + "minimum": 0 + }, + "per_page": { + "description": "Items per Page\n\nThis is the number of items per page.", + "type": "integer", + "format": "int64", + "default": 250, + "maximum": 1000, + "minimum": 1 + } + } + }, + "revokedSessions": { + "type": "object", + "properties": { + "count": { + "description": "The number of sessions that were revoked.", + "type": "integer", + "format": "int64" + } + } + }, "selfServiceBrowserLocationChangeRequiredError": { "type": "object", "title": "Is sent when a flow requires a browser to change its location.", @@ -2955,6 +3251,12 @@ } } }, + "sessionList": { + "type": "array", + "items": { + "$ref": "#/definitions/session" + } + }, "settingsProfileFormConfig": { "type": "object", "required": [ diff --git a/x/pagination.go b/x/pagination.go index f1f2e39f4a2e..ddaad8d23ce1 100644 --- a/x/pagination.go +++ b/x/pagination.go @@ -7,6 +7,28 @@ import ( "github.com/ory/x/pagination/pagepagination" ) +// swagger:model pagination +type PaginationParams struct { + // Items per Page + // + // This is the number of items per page. + // + // required: false + // in: query + // default: 250 + // min: 1 + // max: 1000 + PerPage int `json:"per_page"` + + // Pagination Page + // + // required: false + // in: query + // default: 0 + // min: 0 + Page int `json:"page"` +} + const paginationMaxItems = 1000 const paginationDefaultItems = 250