Plug assume organization session

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-21 11:46:29 +01:00
committed by Bryan Frimin
parent 62721f7627
commit 86bdb14289
9 changed files with 564 additions and 178 deletions

View File

@@ -8,11 +8,13 @@ import {
IconClock, IconClock,
IconLock, IconLock,
} from "@probo/ui"; } from "@probo/ui";
import { Link } from "react-router"; import { useNavigate } from "react-router";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { useFragment } from "react-relay"; import { useFragment, useMutation } from "react-relay";
import type { MembershipCardFragment$key } from "./__generated__/MembershipCardFragment.graphql"; import type { MembershipCardFragment$key } from "./__generated__/MembershipCardFragment.graphql";
import { parseDate } from "@probo/helpers"; import { parseDate } from "@probo/helpers";
import { useCallback } from "react";
import type { MembershipCard_assumeMutation } from "./__generated__/MembershipCard_assumeMutation.graphql";
const fragment = graphql` const fragment = graphql`
fragment MembershipCardFragment on Membership { fragment MembershipCardFragment on Membership {
@@ -28,6 +30,25 @@ const fragment = graphql`
} }
`; `;
const assumeOrganizationSessionMutation = graphql`
mutation MembershipCard_assumeMutation(
$input: AssumeOrganizationSessionInput!
) {
assumeOrganizationSession(input: $input) {
result {
__typename
... on PasswordRequired {
reason
}
... on SAMLAuthenticationRequired {
reason
redirectUrl
}
}
}
}
`;
interface MembershipCardProps { interface MembershipCardProps {
fKey: MembershipCardFragment$key; fKey: MembershipCardFragment$key;
} }
@@ -35,6 +56,7 @@ interface MembershipCardProps {
export function MembershipCard(props: MembershipCardProps) { export function MembershipCard(props: MembershipCardProps) {
const { fKey } = props; const { fKey } = props;
const { __ } = useTranslate(); const { __ } = useTranslate();
const navigate = useNavigate();
const { lastSession, organization } = useFragment<MembershipCardFragment$key>( const { lastSession, organization } = useFragment<MembershipCardFragment$key>(
fragment, fragment,
@@ -42,13 +64,40 @@ export function MembershipCard(props: MembershipCardProps) {
); );
const isAuthenticated = !!lastSession; const isAuthenticated = !!lastSession;
const isExpired = const isExpired =
lastSession && parseDate(lastSession.expiresAt) >= new Date(); lastSession && parseDate(lastSession.expiresAt) < new Date();
// Determine target URL and button text based on auth status const [assumeOrganizationSession] =
// const targetUrl = isAuthenticated useMutation<MembershipCard_assumeMutation>(
// ? `/organizations/${organization.id}` assumeOrganizationSessionMutation,
// : organization.loginUrl; );
const targetUrl = `/organizations/${organization.id}`;
const handleAssumeOrganizationSession = useCallback(() => {
assumeOrganizationSession({
variables: {
input: {
organizationId: organization.id,
},
},
onCompleted: ({ assumeOrganizationSession }) => {
if (!assumeOrganizationSession) {
throw new Error("complete mutation result is empty");
}
const { result } = assumeOrganizationSession;
switch (result.__typename) {
case "PasswordRequired":
navigate("auth/login");
break;
case "SAMLAuthenticationRequired":
window.location.href = result.redirectUrl;
break;
default:
navigate(`/organizations/${organization.id}`);
}
},
});
}, [assumeOrganizationSession, navigate, organization.id]);
const getAuthBadge = () => { const getAuthBadge = () => {
if (isAuthenticated) { if (isAuthenticated) {
@@ -75,58 +124,23 @@ export function MembershipCard(props: MembershipCardProps) {
} }
}; };
// const getButtonText = () => {
// if (isAuthenticated) return __("Select");
// if (organization.authenticationMethod === "saml")
// return __("Login with SAML");
// return __("Login");
// };
// Check if the URL is a backend SAML endpoint
const isSAMLUrl = targetUrl.includes("/connect/saml/");
return ( return (
<Card padded className="w-full"> <Card padded className="w-full">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{isSAMLUrl ? ( <div className="flex items-center gap-4 hover:text-primary flex-1">
<a <Avatar
href={targetUrl} src={organization.logoUrl}
className="flex items-center gap-4 hover:text-primary flex-1" name={organization.name}
> size="l"
<Avatar />
src={organization.logoUrl} <div className="flex flex-col gap-1">
name={organization.name} <h2 className="font-semibold text-xl">{organization.name}</h2>
size="l" {getAuthBadge()}
/> </div>
<div className="flex flex-col gap-1"> </div>
<h2 className="font-semibold text-xl">{organization.name}</h2>
{getAuthBadge()}
</div>
</a>
) : (
<Link
to={targetUrl}
className="flex items-center gap-4 hover:text-primary flex-1"
>
<Avatar
src={organization.logoUrl}
name={organization.name}
size="l"
/>
<div className="flex flex-col gap-1">
<h2 className="font-semibold text-xl">{organization.name}</h2>
{getAuthBadge()}
</div>
</Link>
)}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button asChild> <Button onClick={handleAssumeOrganizationSession}>
{/* {isSAMLUrl ? ( {__("Login")}
<a href={targetUrl}>{getButtonText()}</a>
) : (
<Link to={targetUrl}>{getButtonText()}</Link>
)} */}
<Link to={targetUrl}>LOGIN</Link>
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -7,8 +7,11 @@ import {
} from "@probo/ui"; } from "@probo/ui";
import { graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import type { OrganizationDropdownMenuItemFragment$key } from "./__generated__/OrganizationDropdownMenuItemFragment.graphql"; import type { OrganizationDropdownMenuItemFragment$key } from "./__generated__/OrganizationDropdownMenuItemFragment.graphql";
import { useFragment } from "react-relay"; import { useFragment, useMutation } from "react-relay";
import { parseDate } from "@probo/helpers"; import { parseDate } from "@probo/helpers";
import { useCallback } from "react";
import { useNavigate } from "react-router";
import type { OrganizationDropdownMenuItem_assumeMutation } from "./__generated__/OrganizationDropdownMenuItem_assumeMutation.graphql";
const fragment = graphql` const fragment = graphql`
fragment OrganizationDropdownMenuItemFragment on Membership { fragment OrganizationDropdownMenuItemFragment on Membership {
@@ -18,25 +21,81 @@ const fragment = graphql`
expiresAt expiresAt
} }
organization @required(action: THROW) { organization @required(action: THROW) {
id
logoUrl logoUrl
name name
} }
} }
`; `;
const assumeOrganizationSessionMutation = graphql`
mutation OrganizationDropdownMenuItem_assumeMutation(
$input: AssumeOrganizationSessionInput!
) {
assumeOrganizationSession(input: $input) {
result {
__typename
... on PasswordRequired {
reason
}
... on SAMLAuthenticationRequired {
reason
redirectUrl
}
}
}
}
`;
export function OrganizationDropdownMenuItem(props: { export function OrganizationDropdownMenuItem(props: {
fKey: OrganizationDropdownMenuItemFragment$key; fKey: OrganizationDropdownMenuItemFragment$key;
}) { }) {
const { fKey } = props; const { fKey } = props;
const navigate = useNavigate();
const { id, lastSession, organization } = const { id, lastSession, organization } =
useFragment<OrganizationDropdownMenuItemFragment$key>(fragment, fKey); useFragment<OrganizationDropdownMenuItemFragment$key>(fragment, fKey);
const isAuthenticated = !!lastSession; const isAuthenticated = !!lastSession;
const isExpired = const isExpired =
lastSession && parseDate(lastSession.expiresAt) >= new Date(); lastSession && parseDate(lastSession.expiresAt) < new Date();
const [assumeOrganizationSession] =
useMutation<OrganizationDropdownMenuItem_assumeMutation>(
assumeOrganizationSessionMutation,
);
const handleAssumeOrganizationSession = useCallback(() => {
assumeOrganizationSession({
variables: {
input: {
organizationId: organization.id,
},
},
onCompleted: ({ assumeOrganizationSession }) => {
if (!assumeOrganizationSession) {
throw new Error("complete mutation result is empty");
}
const { result } = assumeOrganizationSession;
switch (result.__typename) {
case "PasswordRequired":
navigate("auth/login");
break;
case "SAMLAuthenticationRequired":
window.location.href = result.redirectUrl;
break;
default:
navigate(`/organizations/${organization.id}`);
}
},
});
}, [assumeOrganizationSession, navigate, organization.id]);
return ( return (
<DropdownItem key={id}> <DropdownItem key={id} onClick={handleAssumeOrganizationSession}>
{/* TODO add link or anchor */} {/* TODO add link or anchor */}
<Avatar name={organization.name} src={organization.logoUrl} /> <Avatar name={organization.name} src={organization.logoUrl} />
<span className="flex-1">{organization.name}</span> <span className="flex-1">{organization.name}</span>

View File

@@ -0,0 +1,145 @@
/**
* @generated SignedSource<<2574b3b6a557336992dff4f923fc0870>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type ReauthenticationReason = "POLICY_REQUIREMENT" | "SENSITIVE_ACTION" | "SESSION_EXPIRED";
export type AssumeOrganizationSessionInput = {
organizationId: string;
};
export type MembershipCard_assumeMutation$variables = {
input: AssumeOrganizationSessionInput;
};
export type MembershipCard_assumeMutation$data = {
readonly assumeOrganizationSession: {
readonly result: {
readonly __typename: "PasswordRequired";
readonly reason: ReauthenticationReason;
} | {
readonly __typename: "SAMLAuthenticationRequired";
readonly reason: ReauthenticationReason;
readonly redirectUrl: string;
} | {
// This will never be '%other', but we need some
// value in case none of the concrete values match.
readonly __typename: "%other";
};
} | null | undefined;
};
export type MembershipCard_assumeMutation = {
response: MembershipCard_assumeMutation$data;
variables: MembershipCard_assumeMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reason",
"storageKey": null
},
v2 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "AssumeOrganizationSessionPayload",
"kind": "LinkedField",
"name": "assumeOrganizationSession",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": null,
"kind": "LinkedField",
"name": "result",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
{
"kind": "InlineFragment",
"selections": [
(v1/*: any*/)
],
"type": "PasswordRequired",
"abstractKey": null
},
{
"kind": "InlineFragment",
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUrl",
"storageKey": null
}
],
"type": "SAMLAuthenticationRequired",
"abstractKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "MembershipCard_assumeMutation",
"selections": (v2/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "MembershipCard_assumeMutation",
"selections": (v2/*: any*/)
},
"params": {
"cacheID": "0d93c6ee32107a4263a910e21597e982",
"id": null,
"metadata": {},
"name": "MembershipCard_assumeMutation",
"operationKind": "mutation",
"text": "mutation MembershipCard_assumeMutation(\n $input: AssumeOrganizationSessionInput!\n) {\n assumeOrganizationSession(input: $input) {\n result {\n __typename\n ... on PasswordRequired {\n reason\n }\n ... on SAMLAuthenticationRequired {\n reason\n redirectUrl\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "aa7a5daec98d5fa51a56da0a7e0b2178";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<ef66c9eab961d5c3c28669629b82cfd6>> * @generated SignedSource<<e917cb1af13350772d46c02bd6b37da5>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -17,6 +17,7 @@ export type OrganizationDropdownMenuItemFragment$data = {
readonly id: string; readonly id: string;
} | null | undefined; } | null | undefined;
readonly organization: { readonly organization: {
readonly id: string;
readonly logoUrl: string | null | undefined; readonly logoUrl: string | null | undefined;
readonly name: string; readonly name: string;
}; };
@@ -71,6 +72,7 @@ return {
"name": "organization", "name": "organization",
"plural": false, "plural": false,
"selections": [ "selections": [
(v0/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -96,6 +98,6 @@ return {
}; };
})(); })();
(node as any).hash = "6be7f721fd63d6d758b479e58c68b754"; (node as any).hash = "22fea7c8463774a5fa9ea5f56d1e876c";
export default node; export default node;

View File

@@ -0,0 +1,145 @@
/**
* @generated SignedSource<<469707d0eca0a0378fa589809db964bc>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type ReauthenticationReason = "POLICY_REQUIREMENT" | "SENSITIVE_ACTION" | "SESSION_EXPIRED";
export type AssumeOrganizationSessionInput = {
organizationId: string;
};
export type OrganizationDropdownMenuItem_assumeMutation$variables = {
input: AssumeOrganizationSessionInput;
};
export type OrganizationDropdownMenuItem_assumeMutation$data = {
readonly assumeOrganizationSession: {
readonly result: {
readonly __typename: "PasswordRequired";
readonly reason: ReauthenticationReason;
} | {
readonly __typename: "SAMLAuthenticationRequired";
readonly reason: ReauthenticationReason;
readonly redirectUrl: string;
} | {
// This will never be '%other', but we need some
// value in case none of the concrete values match.
readonly __typename: "%other";
};
} | null | undefined;
};
export type OrganizationDropdownMenuItem_assumeMutation = {
response: OrganizationDropdownMenuItem_assumeMutation$data;
variables: OrganizationDropdownMenuItem_assumeMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reason",
"storageKey": null
},
v2 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "AssumeOrganizationSessionPayload",
"kind": "LinkedField",
"name": "assumeOrganizationSession",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": null,
"kind": "LinkedField",
"name": "result",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
{
"kind": "InlineFragment",
"selections": [
(v1/*: any*/)
],
"type": "PasswordRequired",
"abstractKey": null
},
{
"kind": "InlineFragment",
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "redirectUrl",
"storageKey": null
}
],
"type": "SAMLAuthenticationRequired",
"abstractKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "OrganizationDropdownMenuItem_assumeMutation",
"selections": (v2/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "OrganizationDropdownMenuItem_assumeMutation",
"selections": (v2/*: any*/)
},
"params": {
"cacheID": "024d06c3bba88bb18eb1aa7e2888edd0",
"id": null,
"metadata": {},
"name": "OrganizationDropdownMenuItem_assumeMutation",
"operationKind": "mutation",
"text": "mutation OrganizationDropdownMenuItem_assumeMutation(\n $input: AssumeOrganizationSessionInput!\n) {\n assumeOrganizationSession(input: $input) {\n result {\n __typename\n ... on PasswordRequired {\n reason\n }\n ... on SAMLAuthenticationRequired {\n reason\n redirectUrl\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "bbc8443e4a756b37f8b46ee98972bdcb";
export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<2bde2fba4e4897436af08d9dd700b265>> * @generated SignedSource<<034b9a96de4b1efda9529e8165779342>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -248,12 +248,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "8338998501d59b0036d1eaf98dff1ffb", "cacheID": "b880e24e59e2bb500bbb2b9026423c20",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "OrganizationDropdownMenuQuery", "name": "OrganizationDropdownMenuQuery",
"operationKind": "query", "operationKind": "query",
"text": "query OrganizationDropdownMenuQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n organization {\n name\n id\n }\n ...OrganizationDropdownMenuItemFragment\n }\n }\n }\n id\n }\n}\n\nfragment OrganizationDropdownMenuItemFragment on Membership {\n id\n lastSession {\n id\n expiresAt\n }\n organization {\n logoUrl\n name\n id\n }\n}\n" "text": "query OrganizationDropdownMenuQuery {\n viewer {\n memberships(first: 1000, orderBy: {direction: DESC, field: CREATED_AT}) {\n edges {\n node {\n id\n organization {\n name\n id\n }\n ...OrganizationDropdownMenuItemFragment\n }\n }\n }\n id\n }\n}\n\nfragment OrganizationDropdownMenuItemFragment on Membership {\n id\n lastSession {\n id\n expiresAt\n }\n organization {\n id\n logoUrl\n name\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -62,18 +62,32 @@ func (m Membership) CursorKey(orderBy MembershipOrderField) page.CursorKey {
func (m *Membership) LoadByIdentityInOrganization(ctx context.Context, conn pg.Conn, identityID gid.GID, organizationID gid.GID) error { func (m *Membership) LoadByIdentityInOrganization(ctx context.Context, conn pg.Conn, identityID gid.GID, organizationID gid.GID) error {
q := ` q := `
WITH mbr AS (
SELECT
id,
identity_id,
organization_id,
role,
created_at,
updated_at
FROM
iam_memberships
WHERE
identity_id = @identity_id
AND organization_id = @organization_id
)
SELECT SELECT
id, mbr.id,
identity_id, mbr.identity_id,
organization_id, mbr.organization_id,
role, mbr.role,
created_at, i.fullname AS full_name,
updated_at i.email_address,
mbr.created_at,
mbr.updated_at
FROM FROM
iam_memberships mbr
WHERE JOIN identities i ON mbr.identity_id = i.id
identity_id = @identity_id
AND organization_id = @organization_id
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
@@ -157,18 +171,18 @@ func (m *Membership) LoadByID(
) error { ) error {
query := ` query := `
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
id, id,
identity_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
updated_at updated_at
FROM FROM
iam_memberships iam_memberships
WHERE WHERE
id = @membership_id id = @membership_id
AND %s AND %s
) )
SELECT SELECT
mbr.id, mbr.id,
@@ -237,19 +251,19 @@ func (m *Membership) LoadRoleByIdentityAndEntityID(
query := fmt.Sprintf(` query := fmt.Sprintf(`
SELECT SELECT
m.id, m.id,
m.identity_id, m.identity_id,
m.organization_id, m.organization_id,
m.role, m.role,
m.created_at, m.created_at,
m.updated_at m.updated_at
FROM FROM
iam_memberships m iam_memberships m
INNER JOIN %s e ON e.id = @entity_id INNER JOIN %s e ON e.id = @entity_id
WHERE WHERE
%s %s
AND m.identity_id = @identity_id AND m.identity_id = @identity_id
AND m.organization_id = e.organization_id AND m.organization_id = e.organization_id
LIMIT 1; LIMIT 1;
`, tableName, scopeFragment) `, tableName, scopeFragment)
@@ -296,19 +310,19 @@ func (m *Membership) LoadByIdentityAndOrg(
) error { ) error {
q := ` q := `
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
am.id, am.id,
am.identity_id, am.identity_id,
am.organization_id, am.organization_id,
am.role, am.role,
am.created_at, am.created_at,
am.updated_at am.updated_at
FROM FROM
iam_memberships am iam_memberships am
WHERE WHERE
am.identity_id = @identity_id am.identity_id = @identity_id
AND am.organization_id = @organization_id AND am.organization_id = @organization_id
AND %s AND %s
) )
SELECT SELECT
mbr.id, mbr.id,
@@ -389,7 +403,7 @@ func (m *Membership) Delete(ctx context.Context, conn pg.Conn, scope Scoper, mem
DELETE FROM DELETE FROM
iam_memberships iam_memberships
WHERE WHERE
%s %s
AND id = @membership_id AND id = @membership_id
` `
@@ -421,20 +435,20 @@ func (m *Memberships) LoadByIdentityID(
) error { ) error {
query := ` query := `
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
id, id,
identity_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
updated_at updated_at
FROM FROM
iam_memberships iam_memberships
WHERE WHERE
identity_id = @identity_id identity_id = @identity_id
AND %s AND %s
ORDER BY ORDER BY
created_at DESC created_at DESC
) )
SELECT SELECT
mbr.id, mbr.id,
@@ -483,18 +497,18 @@ func (m *Memberships) LoadByOrganizationID(
) error { ) error {
query := ` query := `
WITH mbr AS ( WITH mbr AS (
SELECT SELECT
id, id,
identity_id, identity_id,
organization_id, organization_id,
role, role,
created_at, created_at,
updated_at updated_at
FROM FROM
iam_memberships iam_memberships
WHERE WHERE
organization_id = @organization_id organization_id = @organization_id
AND %s AND %s
) )
SELECT SELECT
id, id,
@@ -506,19 +520,19 @@ SELECT
created_at, created_at,
updated_at updated_at
FROM ( FROM (
SELECT SELECT
mbr.id, mbr.id,
mbr.identity_id, mbr.identity_id,
mbr.organization_id, mbr.organization_id,
mbr.role, mbr.role,
i.fullname as full_name, i.fullname as full_name,
i.email_address, i.email_address,
mbr.created_at, mbr.created_at,
mbr.updated_at mbr.updated_at
FROM FROM
mbr mbr
JOIN JOIN
identities i ON mbr.identity_id = i.id identities i ON mbr.identity_id = i.id
) AS membership_with_identity ) AS membership_with_identity
WHERE %s WHERE %s
` `

View File

@@ -18,6 +18,7 @@ import (
"context" "context"
"crypto/x509" "crypto/x509"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"maps" "maps"
"time" "time"
@@ -125,6 +126,10 @@ LIMIT 1;
config, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[SAMLConfiguration]) config, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[SAMLConfiguration])
if err != nil { if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return ErrResourceNotFound
}
return fmt.Errorf("cannot collect saml_configuration: %w", err) return fmt.Errorf("cannot collect saml_configuration: %w", err)
} }

View File

@@ -195,9 +195,9 @@ UPDATE iam_sessions
SET SET
expired_at = @expired_at, expired_at = @expired_at,
updated_at = @updated_at, updated_at = @updated_at,
user_agent = @user_agent, user_agent = @user_agent,
ip_address = @ip_address, ip_address = @ip_address,
expire_reason = @expire_reason, expire_reason = @expire_reason,
data = @data data = @data
WHERE WHERE
id = @session_id id = @session_id
@@ -246,7 +246,7 @@ FROM
iam_sessions iam_sessions
WHERE WHERE
identity_id = @identity_id identity_id = @identity_id
AND %s AND %s
` `
q = fmt.Sprintf(q, cursor.SQLFragment()) q = fmt.Sprintf(q, cursor.SQLFragment())
@@ -272,12 +272,12 @@ WHERE
func (s *Sessions) CountByIdentityID(ctx context.Context, conn pg.Conn, identityID gid.GID) (int, error) { func (s *Sessions) CountByIdentityID(ctx context.Context, conn pg.Conn, identityID gid.GID) (int, error) {
q := ` q := `
SELECT SELECT
COUNT(*) COUNT(*)
FROM FROM
iam_sessions iam_sessions
WHERE WHERE
identity_id = @identity_id identity_id = @identity_id
` `
args := pgx.StrictNamedArgs{"identity_id": identityID} args := pgx.StrictNamedArgs{"identity_id": identityID}
@@ -297,11 +297,11 @@ UPDATE iam_sessions
SET SET
expired_at = NOW(), expired_at = NOW(),
updated_at = NOW(), updated_at = NOW(),
expire_reason = 'revoked' expire_reason = 'revoked'
WHERE WHERE
id != @session_id id != @session_id
AND identity_id = @identity_id AND identity_id = @identity_id
AND expire_reason IS NULL AND expire_reason IS NULL
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{
@@ -320,25 +320,27 @@ WHERE
func (s *Session) LoadByRootSessionIDAndMembershipID(ctx context.Context, conn pg.Conn, rootSessionID gid.GID, membershipID gid.GID) error { func (s *Session) LoadByRootSessionIDAndMembershipID(ctx context.Context, conn pg.Conn, rootSessionID gid.GID, membershipID gid.GID) error {
q := ` q := `
SELECT SELECT
id, id,
identity_id, identity_id,
tenant_id, tenant_id,
membership_id, membership_id,
data, data,
parent_session_id, parent_session_id,
auth_method, auth_method,
authenticated_at, authenticated_at,
expire_reason, expire_reason,
user_agent, user_agent,
ip_address, ip_address,
expired_at, expired_at,
created_at, created_at,
updated_at updated_at
FROM FROM
iam_sessions iam_sessions
WHERE WHERE
parent_session_id = @root_session_id parent_session_id = @root_session_id
AND membership_id = @membership_id AND membership_id = @membership_id
ORDER BY created_at DESC
LIMIT 1
` `
args := pgx.StrictNamedArgs{ args := pgx.StrictNamedArgs{