mirror of
https://codeberg.org/MarkusThielker/next-ory.git
synced 2025-04-18 00:21:18 +00:00
commit
7961b50adb
22 changed files with 990 additions and 432 deletions
|
@ -1,334 +1,334 @@
|
||||||
import { relations } from 'drizzle-orm/relations';
|
import { relations } from 'drizzle-orm/relations';
|
||||||
import {
|
import {
|
||||||
continuityContainers,
|
continuity_containers,
|
||||||
courierMessageDispatches,
|
courier_message_dispatches,
|
||||||
courierMessages,
|
courier_messages,
|
||||||
identities,
|
identities,
|
||||||
identityCredentialIdentifiers,
|
identity_credential_identifiers,
|
||||||
identityCredentials,
|
identity_credential_types,
|
||||||
identityCredentialTypes,
|
identity_credentials,
|
||||||
identityLoginCodes,
|
identity_login_codes,
|
||||||
identityRecoveryAddresses,
|
identity_recovery_addresses,
|
||||||
identityRecoveryCodes,
|
identity_recovery_codes,
|
||||||
identityRecoveryTokens,
|
identity_recovery_tokens,
|
||||||
identityRegistrationCodes,
|
identity_registration_codes,
|
||||||
identityVerifiableAddresses,
|
identity_verifiable_addresses,
|
||||||
identityVerificationCodes,
|
identity_verification_codes,
|
||||||
identityVerificationTokens,
|
identity_verification_tokens,
|
||||||
networks,
|
networks,
|
||||||
selfserviceErrors,
|
selfservice_errors,
|
||||||
selfserviceLoginFlows,
|
selfservice_login_flows,
|
||||||
selfserviceRecoveryFlows,
|
selfservice_recovery_flows,
|
||||||
selfserviceRegistrationFlows,
|
selfservice_registration_flows,
|
||||||
selfserviceSettingsFlows,
|
selfservice_settings_flows,
|
||||||
selfserviceVerificationFlows,
|
selfservice_verification_flows,
|
||||||
sessionDevices,
|
session_devices,
|
||||||
sessions,
|
sessions,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
|
|
||||||
export const identityCredentialsRelations = relations(identityCredentials, ({ one, many }) => ({
|
export const identityCredentialsRelations = relations(identity_credentials, ({ one, many }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [identityCredentials.identityId],
|
fields: [identity_credentials.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
identityCredentialType: one(identityCredentialTypes, {
|
identityCredentialType: one(identity_credential_types, {
|
||||||
fields: [identityCredentials.identityCredentialTypeId],
|
fields: [identity_credentials.identity_credential_type_id],
|
||||||
references: [identityCredentialTypes.id],
|
references: [identity_credential_types.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityCredentials.nid],
|
fields: [identity_credentials.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityCredentialIdentifiers: many(identityCredentialIdentifiers),
|
identityCredentialIdentifiers: many(identity_credential_identifiers),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identitiesRelations = relations(identities, ({ one, many }) => ({
|
export const identitiesRelations = relations(identities, ({ one, many }) => ({
|
||||||
identityCredentials: many(identityCredentials),
|
identityCredentials: many(identity_credentials),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identities.nid],
|
fields: [identities.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityVerifiableAddresses: many(identityVerifiableAddresses),
|
identityVerifiableAddresses: many(identity_verifiable_addresses),
|
||||||
selfserviceSettingsFlows: many(selfserviceSettingsFlows),
|
selfserviceSettingsFlows: many(selfservice_settings_flows),
|
||||||
continuityContainers: many(continuityContainers),
|
continuityContainers: many(continuity_containers),
|
||||||
sessions: many(sessions),
|
sessions: many(sessions),
|
||||||
identityRecoveryAddresses: many(identityRecoveryAddresses),
|
identityRecoveryAddresses: many(identity_recovery_addresses),
|
||||||
selfserviceRecoveryFlows: many(selfserviceRecoveryFlows),
|
selfserviceRecoveryFlows: many(selfservice_recovery_flows),
|
||||||
identityRecoveryTokens: many(identityRecoveryTokens),
|
identityRecoveryTokens: many(identity_recovery_tokens),
|
||||||
identityRecoveryCodes: many(identityRecoveryCodes),
|
identityRecoveryCodes: many(identity_recovery_codes),
|
||||||
identityLoginCodes: many(identityLoginCodes),
|
identityLoginCodes: many(identity_login_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityCredentialTypesRelations = relations(identityCredentialTypes, ({ many }) => ({
|
export const identityCredentialTypesRelations = relations(identity_credential_types, ({ many }) => ({
|
||||||
identityCredentials: many(identityCredentials),
|
identityCredentials: many(identity_credentials),
|
||||||
identityCredentialIdentifiers: many(identityCredentialIdentifiers),
|
identityCredentialIdentifiers: many(identity_credential_identifiers),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const networksRelations = relations(networks, ({ many }) => ({
|
export const networksRelations = relations(networks, ({ many }) => ({
|
||||||
identityCredentials: many(identityCredentials),
|
identityCredentials: many(identity_credentials),
|
||||||
selfserviceLoginFlows: many(selfserviceLoginFlows),
|
selfserviceLoginFlows: many(selfservice_login_flows),
|
||||||
selfserviceRegistrationFlows: many(selfserviceRegistrationFlows),
|
selfserviceRegistrationFlows: many(selfservice_registration_flows),
|
||||||
identities: many(identities),
|
identities: many(identities),
|
||||||
identityCredentialIdentifiers: many(identityCredentialIdentifiers),
|
identityCredentialIdentifiers: many(identity_credential_identifiers),
|
||||||
identityVerifiableAddresses: many(identityVerifiableAddresses),
|
identityVerifiableAddresses: many(identity_verifiable_addresses),
|
||||||
courierMessages: many(courierMessages),
|
courierMessages: many(courier_messages),
|
||||||
selfserviceErrors: many(selfserviceErrors),
|
selfserviceErrors: many(selfservice_errors),
|
||||||
selfserviceVerificationFlows: many(selfserviceVerificationFlows),
|
selfserviceVerificationFlows: many(selfservice_verification_flows),
|
||||||
selfserviceSettingsFlows: many(selfserviceSettingsFlows),
|
selfserviceSettingsFlows: many(selfservice_settings_flows),
|
||||||
continuityContainers: many(continuityContainers),
|
continuityContainers: many(continuity_containers),
|
||||||
sessions: many(sessions),
|
sessions: many(sessions),
|
||||||
identityRecoveryAddresses: many(identityRecoveryAddresses),
|
identityRecoveryAddresses: many(identity_recovery_addresses),
|
||||||
identityVerificationTokens: many(identityVerificationTokens),
|
identityVerificationTokens: many(identity_verification_tokens),
|
||||||
selfserviceRecoveryFlows: many(selfserviceRecoveryFlows),
|
selfserviceRecoveryFlows: many(selfservice_recovery_flows),
|
||||||
identityRecoveryTokens: many(identityRecoveryTokens),
|
identityRecoveryTokens: many(identity_recovery_tokens),
|
||||||
identityRecoveryCodes: many(identityRecoveryCodes),
|
identityRecoveryCodes: many(identity_recovery_codes),
|
||||||
sessionDevices: many(sessionDevices),
|
sessionDevices: many(session_devices),
|
||||||
identityVerificationCodes: many(identityVerificationCodes),
|
identityVerificationCodes: many(identity_verification_codes),
|
||||||
courierMessageDispatches: many(courierMessageDispatches),
|
courierMessageDispatches: many(courier_message_dispatches),
|
||||||
identityLoginCodes: many(identityLoginCodes),
|
identityLoginCodes: many(identity_login_codes),
|
||||||
identityRegistrationCodes: many(identityRegistrationCodes),
|
identityRegistrationCodes: many(identity_registration_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const selfserviceLoginFlowsRelations = relations(selfserviceLoginFlows, ({ one, many }) => ({
|
export const selfserviceLoginFlowsRelations = relations(selfservice_login_flows, ({ one, many }) => ({
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [selfserviceLoginFlows.nid],
|
fields: [selfservice_login_flows.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityLoginCodes: many(identityLoginCodes),
|
identityLoginCodes: many(identity_login_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const selfserviceRegistrationFlowsRelations = relations(selfserviceRegistrationFlows, ({ one, many }) => ({
|
export const selfserviceRegistrationFlowsRelations = relations(selfservice_registration_flows, ({ one, many }) => ({
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [selfserviceRegistrationFlows.nid],
|
fields: [selfservice_registration_flows.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityRegistrationCodes: many(identityRegistrationCodes),
|
identityRegistrationCodes: many(identity_registration_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityCredentialIdentifiersRelations = relations(identityCredentialIdentifiers, ({ one }) => ({
|
export const identityCredentialIdentifiersRelations = relations(identity_credential_identifiers, ({ one }) => ({
|
||||||
identityCredential: one(identityCredentials, {
|
identityCredential: one(identity_credentials, {
|
||||||
fields: [identityCredentialIdentifiers.identityCredentialId],
|
fields: [identity_credential_identifiers.identity_credential_id],
|
||||||
references: [identityCredentials.id],
|
references: [identity_credentials.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityCredentialIdentifiers.nid],
|
fields: [identity_credential_identifiers.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityCredentialType: one(identityCredentialTypes, {
|
identityCredentialType: one(identity_credential_types, {
|
||||||
fields: [identityCredentialIdentifiers.identityCredentialTypeId],
|
fields: [identity_credential_identifiers.identity_credential_type_id],
|
||||||
references: [identityCredentialTypes.id],
|
references: [identity_credential_types.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityVerifiableAddressesRelations = relations(identityVerifiableAddresses, ({ one, many }) => ({
|
export const identityVerifiableAddressesRelations = relations(identity_verifiable_addresses, ({ one, many }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [identityVerifiableAddresses.identityId],
|
fields: [identity_verifiable_addresses.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityVerifiableAddresses.nid],
|
fields: [identity_verifiable_addresses.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityVerificationTokens: many(identityVerificationTokens),
|
identityVerificationTokens: many(identity_verification_tokens),
|
||||||
identityVerificationCodes: many(identityVerificationCodes),
|
identityVerificationCodes: many(identity_verification_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const courierMessagesRelations = relations(courierMessages, ({ one, many }) => ({
|
export const courierMessagesRelations = relations(courier_messages, ({ one, many }) => ({
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [courierMessages.nid],
|
fields: [courier_messages.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
courierMessageDispatches: many(courierMessageDispatches),
|
courierMessageDispatches: many(courier_message_dispatches),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const selfserviceErrorsRelations = relations(selfserviceErrors, ({ one }) => ({
|
export const selfserviceErrorsRelations = relations(selfservice_errors, ({ one }) => ({
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [selfserviceErrors.nid],
|
fields: [selfservice_errors.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const selfserviceVerificationFlowsRelations = relations(selfserviceVerificationFlows, ({ one, many }) => ({
|
export const selfserviceVerificationFlowsRelations = relations(selfservice_verification_flows, ({ one, many }) => ({
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [selfserviceVerificationFlows.nid],
|
fields: [selfservice_verification_flows.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityVerificationTokens: many(identityVerificationTokens),
|
identityVerificationTokens: many(identity_verification_tokens),
|
||||||
identityVerificationCodes: many(identityVerificationCodes),
|
identityVerificationCodes: many(identity_verification_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const selfserviceSettingsFlowsRelations = relations(selfserviceSettingsFlows, ({ one }) => ({
|
export const selfserviceSettingsFlowsRelations = relations(selfservice_settings_flows, ({ one }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [selfserviceSettingsFlows.identityId],
|
fields: [selfservice_settings_flows.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [selfserviceSettingsFlows.nid],
|
fields: [selfservice_settings_flows.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const continuityContainersRelations = relations(continuityContainers, ({ one }) => ({
|
export const continuityContainersRelations = relations(continuity_containers, ({ one }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [continuityContainers.identityId],
|
fields: [continuity_containers.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [continuityContainers.nid],
|
fields: [continuity_containers.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const sessionsRelations = relations(sessions, ({ one, many }) => ({
|
export const sessionsRelations = relations(sessions, ({ one, many }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [sessions.identityId],
|
fields: [sessions.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [sessions.nid],
|
fields: [sessions.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
sessionDevices: many(sessionDevices),
|
sessionDevices: many(session_devices),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityRecoveryAddressesRelations = relations(identityRecoveryAddresses, ({ one, many }) => ({
|
export const identityRecoveryAddressesRelations = relations(identity_recovery_addresses, ({ one, many }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [identityRecoveryAddresses.identityId],
|
fields: [identity_recovery_addresses.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityRecoveryAddresses.nid],
|
fields: [identity_recovery_addresses.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityRecoveryTokens: many(identityRecoveryTokens),
|
identityRecoveryTokens: many(identity_recovery_tokens),
|
||||||
identityRecoveryCodes: many(identityRecoveryCodes),
|
identityRecoveryCodes: many(identity_recovery_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityVerificationTokensRelations = relations(identityVerificationTokens, ({ one }) => ({
|
export const identityVerificationTokensRelations = relations(identity_verification_tokens, ({ one }) => ({
|
||||||
identityVerifiableAddress: one(identityVerifiableAddresses, {
|
identityVerifiableAddress: one(identity_verifiable_addresses, {
|
||||||
fields: [identityVerificationTokens.identityVerifiableAddressId],
|
fields: [identity_verification_tokens.identity_verifiable_address_id],
|
||||||
references: [identityVerifiableAddresses.id],
|
references: [identity_verifiable_addresses.id],
|
||||||
}),
|
}),
|
||||||
selfserviceVerificationFlow: one(selfserviceVerificationFlows, {
|
selfserviceVerificationFlow: one(selfservice_verification_flows, {
|
||||||
fields: [identityVerificationTokens.selfserviceVerificationFlowId],
|
fields: [identity_verification_tokens.selfservice_verification_flow_id],
|
||||||
references: [selfserviceVerificationFlows.id],
|
references: [selfservice_verification_flows.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityVerificationTokens.nid],
|
fields: [identity_verification_tokens.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const selfserviceRecoveryFlowsRelations = relations(selfserviceRecoveryFlows, ({ one, many }) => ({
|
export const selfserviceRecoveryFlowsRelations = relations(selfservice_recovery_flows, ({ one, many }) => ({
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [selfserviceRecoveryFlows.recoveredIdentityId],
|
fields: [selfservice_recovery_flows.recovered_identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [selfserviceRecoveryFlows.nid],
|
fields: [selfservice_recovery_flows.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityRecoveryTokens: many(identityRecoveryTokens),
|
identityRecoveryTokens: many(identity_recovery_tokens),
|
||||||
identityRecoveryCodes: many(identityRecoveryCodes),
|
identityRecoveryCodes: many(identity_recovery_codes),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityRecoveryTokensRelations = relations(identityRecoveryTokens, ({ one }) => ({
|
export const identityRecoveryTokensRelations = relations(identity_recovery_tokens, ({ one }) => ({
|
||||||
selfserviceRecoveryFlow: one(selfserviceRecoveryFlows, {
|
selfserviceRecoveryFlow: one(selfservice_recovery_flows, {
|
||||||
fields: [identityRecoveryTokens.selfserviceRecoveryFlowId],
|
fields: [identity_recovery_tokens.selfservice_recovery_flow_id],
|
||||||
references: [selfserviceRecoveryFlows.id],
|
references: [selfservice_recovery_flows.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityRecoveryTokens.nid],
|
fields: [identity_recovery_tokens.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identityRecoveryAddress: one(identityRecoveryAddresses, {
|
identityRecoveryAddress: one(identity_recovery_addresses, {
|
||||||
fields: [identityRecoveryTokens.identityRecoveryAddressId],
|
fields: [identity_recovery_tokens.identity_recovery_address_id],
|
||||||
references: [identityRecoveryAddresses.id],
|
references: [identity_recovery_addresses.id],
|
||||||
}),
|
}),
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [identityRecoveryTokens.identityId],
|
fields: [identity_recovery_tokens.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityRecoveryCodesRelations = relations(identityRecoveryCodes, ({ one }) => ({
|
export const identityRecoveryCodesRelations = relations(identity_recovery_codes, ({ one }) => ({
|
||||||
identityRecoveryAddress: one(identityRecoveryAddresses, {
|
identityRecoveryAddress: one(identity_recovery_addresses, {
|
||||||
fields: [identityRecoveryCodes.identityRecoveryAddressId],
|
fields: [identity_recovery_codes.identity_recovery_address_id],
|
||||||
references: [identityRecoveryAddresses.id],
|
references: [identity_recovery_addresses.id],
|
||||||
}),
|
}),
|
||||||
selfserviceRecoveryFlow: one(selfserviceRecoveryFlows, {
|
selfserviceRecoveryFlow: one(selfservice_recovery_flows, {
|
||||||
fields: [identityRecoveryCodes.selfserviceRecoveryFlowId],
|
fields: [identity_recovery_codes.selfservice_recovery_flow_id],
|
||||||
references: [selfserviceRecoveryFlows.id],
|
references: [selfservice_recovery_flows.id],
|
||||||
}),
|
}),
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [identityRecoveryCodes.identityId],
|
fields: [identity_recovery_codes.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityRecoveryCodes.nid],
|
fields: [identity_recovery_codes.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const sessionDevicesRelations = relations(sessionDevices, ({ one }) => ({
|
export const sessionDevicesRelations = relations(session_devices, ({ one }) => ({
|
||||||
session: one(sessions, {
|
session: one(sessions, {
|
||||||
fields: [sessionDevices.sessionId],
|
fields: [session_devices.session_id],
|
||||||
references: [sessions.id],
|
references: [sessions.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [sessionDevices.nid],
|
fields: [session_devices.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityVerificationCodesRelations = relations(identityVerificationCodes, ({ one }) => ({
|
export const identityVerificationCodesRelations = relations(identity_verification_codes, ({ one }) => ({
|
||||||
identityVerifiableAddress: one(identityVerifiableAddresses, {
|
identityVerifiableAddress: one(identity_verifiable_addresses, {
|
||||||
fields: [identityVerificationCodes.identityVerifiableAddressId],
|
fields: [identity_verification_codes.identity_verifiable_address_id],
|
||||||
references: [identityVerifiableAddresses.id],
|
references: [identity_verifiable_addresses.id],
|
||||||
}),
|
}),
|
||||||
selfserviceVerificationFlow: one(selfserviceVerificationFlows, {
|
selfserviceVerificationFlow: one(selfservice_verification_flows, {
|
||||||
fields: [identityVerificationCodes.selfserviceVerificationFlowId],
|
fields: [identity_verification_codes.selfservice_verification_flow_id],
|
||||||
references: [selfserviceVerificationFlows.id],
|
references: [selfservice_verification_flows.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityVerificationCodes.nid],
|
fields: [identity_verification_codes.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const courierMessageDispatchesRelations = relations(courierMessageDispatches, ({ one }) => ({
|
export const courierMessageDispatchesRelations = relations(courier_message_dispatches, ({ one }) => ({
|
||||||
courierMessage: one(courierMessages, {
|
courierMessage: one(courier_messages, {
|
||||||
fields: [courierMessageDispatches.messageId],
|
fields: [courier_message_dispatches.message_id],
|
||||||
references: [courierMessages.id],
|
references: [courier_messages.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [courierMessageDispatches.nid],
|
fields: [courier_message_dispatches.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityLoginCodesRelations = relations(identityLoginCodes, ({ one }) => ({
|
export const identityLoginCodesRelations = relations(identity_login_codes, ({ one }) => ({
|
||||||
selfserviceLoginFlow: one(selfserviceLoginFlows, {
|
selfserviceLoginFlow: one(selfservice_login_flows, {
|
||||||
fields: [identityLoginCodes.selfserviceLoginFlowId],
|
fields: [identity_login_codes.selfservice_login_flow_id],
|
||||||
references: [selfserviceLoginFlows.id],
|
references: [selfservice_login_flows.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityLoginCodes.nid],
|
fields: [identity_login_codes.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
identity: one(identities, {
|
identity: one(identities, {
|
||||||
fields: [identityLoginCodes.identityId],
|
fields: [identity_login_codes.identity_id],
|
||||||
references: [identities.id],
|
references: [identities.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const identityRegistrationCodesRelations = relations(identityRegistrationCodes, ({ one }) => ({
|
export const identityRegistrationCodesRelations = relations(identity_registration_codes, ({ one }) => ({
|
||||||
selfserviceRegistrationFlow: one(selfserviceRegistrationFlows, {
|
selfserviceRegistrationFlow: one(selfservice_registration_flows, {
|
||||||
fields: [identityRegistrationCodes.selfserviceRegistrationFlowId],
|
fields: [identity_registration_codes.selfservice_registration_flow_id],
|
||||||
references: [selfserviceRegistrationFlows.id],
|
references: [selfservice_registration_flows.id],
|
||||||
}),
|
}),
|
||||||
network: one(networks, {
|
network: one(networks, {
|
||||||
fields: [identityRegistrationCodes.nid],
|
fields: [identity_registration_codes.nid],
|
||||||
references: [networks.id],
|
references: [networks.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
|
@ -1,40 +1,21 @@
|
||||||
import { getHydraMetadataApi, getKetoMetadataApi, getKratosMetadataApi } from '@/ory/sdk/server';
|
import { StatusCard } from '@/components/status-card';
|
||||||
import { MetadataApiReady, StatusCard } from '@/components/status-card';
|
import { hydraMetadata, ketoMetadata, kratosMetadata } from '@/lib/action/metadata';
|
||||||
|
import { checkPermission, requirePermission, requireSession } from '@/lib/action/authentication';
|
||||||
|
import InsufficientPermission from '@/components/insufficient-permission';
|
||||||
|
import { permission, relation } from '@/lib/permission';
|
||||||
|
|
||||||
export default async function RootPage() {
|
export default async function RootPage() {
|
||||||
|
|
||||||
const kratosMetadataApi = await getKratosMetadataApi();
|
const session = await requireSession();
|
||||||
const kratosVersion = await kratosMetadataApi.getVersion()
|
const identityId = session.identity!.id;
|
||||||
.then(res => res.data.version)
|
|
||||||
.catch(() => undefined);
|
|
||||||
const kratosStatus = await fetch(process.env.ORY_KRATOS_ADMIN_URL + '/health/ready')
|
|
||||||
.then((response) => response.json() as MetadataApiReady)
|
|
||||||
.catch(() => {
|
|
||||||
return { errors: ['No instance running'] } as MetadataApiReady;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
await requirePermission(permission.stack.dashboard, relation.access, identityId);
|
||||||
|
|
||||||
const hydraMetadataApi = await getHydraMetadataApi();
|
const pmAccessStackStatus = await checkPermission(permission.stack.status, relation.access, identityId);
|
||||||
const hydraVersion = await hydraMetadataApi.getVersion()
|
|
||||||
.then(res => res.data.version)
|
|
||||||
.catch(() => undefined);
|
|
||||||
const hydraStatus = await fetch(process.env.ORY_HYDRA_ADMIN_URL + '/health/ready')
|
|
||||||
.then((response) => response.json() as MetadataApiReady)
|
|
||||||
.catch(() => {
|
|
||||||
return { errors: ['No instance running'] } as MetadataApiReady;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const ketoMetadataApi = await getKetoMetadataApi();
|
|
||||||
const ketoVersion = await ketoMetadataApi.getVersion()
|
|
||||||
.then(res => res.data.version)
|
|
||||||
.catch(() => undefined);
|
|
||||||
const ketoStatus = await fetch(process.env.ORY_KETO_ADMIN_URL + '/health/ready')
|
|
||||||
.then((response) => response.json() as MetadataApiReady)
|
|
||||||
.catch(() => {
|
|
||||||
return { errors: ['No instance running'] } as MetadataApiReady;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const kratos = pmAccessStackStatus && await kratosMetadata();
|
||||||
|
const hydra = pmAccessStackStatus && await hydraMetadata();
|
||||||
|
const keto = pmAccessStackStatus && await ketoMetadata();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
|
@ -43,25 +24,46 @@ export default async function RootPage() {
|
||||||
<p className="text-lg font-light">See the list of all applications in your stack</p>
|
<p className="text-lg font-light">See the list of all applications in your stack</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<StatusCard
|
{
|
||||||
title="Ory Kratos"
|
!pmAccessStackStatus && (
|
||||||
version={kratosVersion}
|
<InsufficientPermission
|
||||||
name="Kratos"
|
permission={permission.stack.status}
|
||||||
status={kratosStatus}
|
relation="access"
|
||||||
className="flex-1"/>
|
identityId={identityId}
|
||||||
<StatusCard
|
classNames="col-span-1 md:col-span-4"
|
||||||
title="Ory Hydra"
|
/>
|
||||||
version={hydraVersion}
|
)
|
||||||
name="Hydra"
|
}
|
||||||
status={hydraStatus}
|
{
|
||||||
className="flex-1"/>
|
kratos && (
|
||||||
<StatusCard
|
<StatusCard
|
||||||
title="Ory Keto"
|
title="Ory Kratos"
|
||||||
version={ketoVersion}
|
version={kratos.version}
|
||||||
name="Keto"
|
name="Kratos"
|
||||||
status={ketoStatus}
|
status={kratos.status}
|
||||||
className="flex-1"/>
|
className="flex-1"/>
|
||||||
<div className="flex-1"/>
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
hydra && (
|
||||||
|
<StatusCard
|
||||||
|
title="Ory Hydra"
|
||||||
|
version={hydra.version}
|
||||||
|
name="Hydra"
|
||||||
|
status={hydra.status}
|
||||||
|
className="flex-1"/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
keto && (
|
||||||
|
<StatusCard
|
||||||
|
title="Ory Keto"
|
||||||
|
version={keto.version}
|
||||||
|
name="Keto"
|
||||||
|
status={keto.status}
|
||||||
|
className="flex-1"/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getIdentityApi } from '@/ory/sdk/server';
|
|
||||||
import { ErrorDisplay } from '@/components/error';
|
import { ErrorDisplay } from '@/components/error';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { IdentityTraits } from '@/components/identity/identity-traits';
|
import { IdentityTraits } from '@/components/identity/identity-traits';
|
||||||
|
@ -11,6 +10,11 @@ import { Badge } from '@/components/ui/badge';
|
||||||
import { Check, X } from 'lucide-react';
|
import { Check, X } from 'lucide-react';
|
||||||
import { IdentityActions } from '@/components/identity/identity-actions';
|
import { IdentityActions } from '@/components/identity/identity-actions';
|
||||||
import { IdentityCredentials } from '@/components/identity/identity-credentials';
|
import { IdentityCredentials } from '@/components/identity/identity-credentials';
|
||||||
|
import { checkPermission, requirePermission, requireSession } from '@/lib/action/authentication';
|
||||||
|
import { permission, relation } from '@/lib/permission';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
import InsufficientPermission from '@/components/insufficient-permission';
|
||||||
|
import { getIdentity, getIdentitySchema, listIdentitySessions } from '@/lib/action/identity';
|
||||||
|
|
||||||
interface MergedAddress {
|
interface MergedAddress {
|
||||||
recovery_id?: string;
|
recovery_id?: string;
|
||||||
|
@ -76,172 +80,229 @@ function mergeAddresses(
|
||||||
|
|
||||||
export default async function UserDetailsPage({ params }: { params: Promise<{ id: string }> }) {
|
export default async function UserDetailsPage({ params }: { params: Promise<{ id: string }> }) {
|
||||||
|
|
||||||
const identityId = (await params).id;
|
const session = await requireSession();
|
||||||
|
const identityId = session.identity!.id;
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
await requirePermission(permission.stack.dashboard, relation.access, identityId);
|
||||||
const identity = await identityApi.getIdentity({ id: identityId })
|
|
||||||
.then((response) => {
|
|
||||||
console.log('identity', response.data);
|
|
||||||
return response.data;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log('Identity not found');
|
|
||||||
});
|
|
||||||
|
|
||||||
const sessions = await identityApi.listIdentitySessions({ id: identityId })
|
const pmAccessUser = await checkPermission(permission.user.it, relation.access, identityId);
|
||||||
.then((response) => response.data)
|
if (!pmAccessUser) {
|
||||||
.catch(() => {
|
return redirect('/user');
|
||||||
console.log('No sessions found');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!identity) {
|
|
||||||
return <ErrorDisplay
|
|
||||||
title="Identity not found"
|
|
||||||
message={`The requested identity with id ${identityId} does not exist`}/>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!identity.verifiable_addresses || !identity.verifiable_addresses[0]) {
|
const pmDeleteUser = await checkPermission(permission.user.it, relation.delete, identityId);
|
||||||
|
const pmAccessUserTrait = await checkPermission(permission.user.trait, relation.access, identityId);
|
||||||
|
const pmEditUserTraits = await checkPermission(permission.user.trait, relation.edit, identityId);
|
||||||
|
const pmAccessUserAddress = await checkPermission(permission.user.address, relation.access, identityId);
|
||||||
|
const pmAccessUserCredential = await checkPermission(permission.user.credential, relation.access, identityId);
|
||||||
|
const pmEditUserState = await checkPermission(permission.user.state, relation.edit, identityId);
|
||||||
|
const pmAccessUserSession = await checkPermission(permission.user.session, relation.access, identityId);
|
||||||
|
const pmDeleteUserSession = await checkPermission(permission.user.session, relation.delete, identityId);
|
||||||
|
const pmCreateUserCode = await checkPermission(permission.user.code, relation.create, identityId);
|
||||||
|
const pmCreateUserLink = await checkPermission(permission.user.link, relation.create, identityId);
|
||||||
|
|
||||||
|
const detailIdentityId = (await params).id;
|
||||||
|
const detailIdentity = pmAccessUser && await getIdentity(detailIdentityId);
|
||||||
|
|
||||||
|
if (!detailIdentity) {
|
||||||
|
return <ErrorDisplay
|
||||||
|
title="Identity not found"
|
||||||
|
message={`The requested identity with id ${detailIdentityId} does not exist`}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!detailIdentity.verifiable_addresses || !detailIdentity.verifiable_addresses[0]) {
|
||||||
return <ErrorDisplay
|
return <ErrorDisplay
|
||||||
title="No verifiable adress"
|
title="No verifiable adress"
|
||||||
message="The identity you are trying to see exists but has no identifiable address"/>;
|
message="The identity you are trying to see exists but has no identifiable address"/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const identitySchema = await identityApi
|
const detailIdentitySessions = pmAccessUserSession && await listIdentitySessions(detailIdentityId);
|
||||||
.getIdentitySchema({ id: identity.schema_id })
|
|
||||||
.then((response) => response.data as KratosSchema);
|
const detailIdentitySchema = await getIdentitySchema(detailIdentity.schema_id)
|
||||||
|
.then((response) => response as KratosSchema);
|
||||||
|
|
||||||
const addresses = mergeAddresses(
|
const addresses = mergeAddresses(
|
||||||
identity.recovery_addresses ?? [],
|
detailIdentity.recovery_addresses ?? [],
|
||||||
identity.verifiable_addresses ?? [],
|
detailIdentity.verifiable_addresses ?? [],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-3xl font-bold leading-tight tracking-tight">{addresses[0].value}</p>
|
<p className="text-3xl font-bold leading-tight tracking-tight">{addresses[0].value}</p>
|
||||||
<p className="text-lg font-light">{identity.id}</p>
|
<p className="text-lg font-light">{detailIdentity.id}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
<Card className="row-span-3">
|
{
|
||||||
<CardHeader>
|
pmAccessUserTrait ?
|
||||||
<CardTitle>Traits</CardTitle>
|
<Card className="row-span-3">
|
||||||
<CardDescription>All identity properties specified in the identity schema</CardDescription>
|
<CardHeader>
|
||||||
</CardHeader>
|
<CardTitle>Traits</CardTitle>
|
||||||
<CardContent>
|
<CardDescription>All identity properties specified in the identity
|
||||||
<IdentityTraits schema={identitySchema} identity={identity}/>
|
schema</CardDescription>
|
||||||
</CardContent>
|
</CardHeader>
|
||||||
</Card>
|
<CardContent>
|
||||||
|
<IdentityTraits
|
||||||
|
schema={detailIdentitySchema}
|
||||||
|
identity={detailIdentity}
|
||||||
|
disabled={!pmEditUserTraits}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
:
|
||||||
|
<InsufficientPermission
|
||||||
|
permission={permission.user.trait}
|
||||||
|
relation={relation.access}
|
||||||
|
identityId={identityId}
|
||||||
|
classNames="row-span-3"/>
|
||||||
|
}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Actions</CardTitle>
|
<CardTitle>Actions</CardTitle>
|
||||||
<CardDescription>Quick actions to manage the identity</CardDescription>
|
<CardDescription>Quick actions to manage the identity</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<IdentityActions identity={identity}/>
|
<IdentityActions
|
||||||
|
identity={detailIdentity}
|
||||||
|
permissions={{
|
||||||
|
pmDeleteUser,
|
||||||
|
pmEditUserState,
|
||||||
|
pmDeleteUserSession,
|
||||||
|
pmCreateUserCode,
|
||||||
|
pmCreateUserLink,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
{
|
||||||
<CardHeader>
|
pmAccessUserAddress ?
|
||||||
<CardTitle>Addresses</CardTitle>
|
<Card>
|
||||||
<CardDescription>All linked addresses for verification and recovery</CardDescription>
|
<CardHeader>
|
||||||
</CardHeader>
|
<CardTitle>Addresses</CardTitle>
|
||||||
<CardContent>
|
<CardDescription>All linked addresses for verification and recovery</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Type</TableHead>
|
<TableHead>Type</TableHead>
|
||||||
<TableHead>Value</TableHead>
|
<TableHead>Value</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{
|
{
|
||||||
addresses.map((address) => {
|
addresses.map((address) => {
|
||||||
return (
|
return (
|
||||||
<TableRow key={address.value}>
|
<TableRow key={address.value}>
|
||||||
<TableCell>{address.value}</TableCell>
|
<TableCell>{address.value}</TableCell>
|
||||||
<TableCell>{address.via}</TableCell>
|
<TableCell>{address.via}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{address.verifiable_id &&
|
{address.verifiable_id &&
|
||||||
<Badge className="m-1 space-x-1">
|
<Badge className="m-1 space-x-1">
|
||||||
<span>Verifiable</span>
|
<span>Verifiable</span>
|
||||||
{
|
{
|
||||||
address.verified ?
|
address.verified ?
|
||||||
<Check className="h-3 w-3"/>
|
<Check className="h-3 w-3"/>
|
||||||
:
|
:
|
||||||
<X className="h-3 w-3"/>
|
<X className="h-3 w-3"/>
|
||||||
|
}
|
||||||
|
</Badge>
|
||||||
}
|
}
|
||||||
</Badge>
|
{address.recovery_id &&
|
||||||
}
|
<Badge className="m-1">Recovery</Badge>
|
||||||
{address.recovery_id &&
|
}
|
||||||
<Badge className="m-1">Recovery</Badge>
|
</TableCell>
|
||||||
}
|
</TableRow>
|
||||||
</TableCell>
|
);
|
||||||
</TableRow>
|
})
|
||||||
);
|
}
|
||||||
})
|
</TableBody>
|
||||||
}
|
</Table>
|
||||||
</TableBody>
|
</CardContent>
|
||||||
</Table>
|
</Card>
|
||||||
</CardContent>
|
:
|
||||||
</Card>
|
<InsufficientPermission
|
||||||
<Card>
|
permission={permission.user.address}
|
||||||
<CardHeader>
|
relation={relation.access}
|
||||||
<CardTitle>Credentials</CardTitle>
|
identityId={identityId}/>
|
||||||
<CardDescription>All authentication mechanisms registered with this identity</CardDescription>
|
}
|
||||||
</CardHeader>
|
{
|
||||||
<CardContent className="space-y-4">
|
pmAccessUserCredential ?
|
||||||
<IdentityCredentials identity={identity}/>
|
<Card>
|
||||||
</CardContent>
|
<CardHeader>
|
||||||
</Card>
|
<CardTitle>Credentials</CardTitle>
|
||||||
<Card>
|
<CardDescription>All authentication mechanisms registered with this
|
||||||
<CardHeader>
|
identity</CardDescription>
|
||||||
<CardTitle>Sessions</CardTitle>
|
</CardHeader>
|
||||||
<CardDescription>See and manage all sessions of this identity</CardDescription>
|
<CardContent className="space-y-4">
|
||||||
</CardHeader>
|
<IdentityCredentials identity={detailIdentity}/>
|
||||||
<CardContent>
|
</CardContent>
|
||||||
<Table>
|
</Card>
|
||||||
<TableHeader>
|
:
|
||||||
<TableRow>
|
<InsufficientPermission
|
||||||
<TableHead>OS</TableHead>
|
permission={permission.user.credential}
|
||||||
<TableHead>Browser</TableHead>
|
relation={relation.access}
|
||||||
<TableHead>Active since</TableHead>
|
identityId={identityId}/>
|
||||||
</TableRow>
|
}
|
||||||
</TableHeader>
|
{
|
||||||
<TableBody>
|
pmAccessUserSession ?
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Sessions</CardTitle>
|
||||||
|
<CardDescription>See and manage all sessions of this identity</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
{
|
{
|
||||||
sessions ?
|
detailIdentitySessions ?
|
||||||
sessions.map((session) => {
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
const device = session.devices![0];
|
<TableRow>
|
||||||
const parser = new UAParser(device.user_agent);
|
<TableHead>OS</TableHead>
|
||||||
const result = parser.getResult();
|
<TableHead>Browser</TableHead>
|
||||||
|
<TableHead>Active since</TableHead>
|
||||||
return (
|
|
||||||
<TableRow key={session.id}>
|
|
||||||
<TableCell className="space-x-1">
|
|
||||||
<span>{result.os.name}</span>
|
|
||||||
<span
|
|
||||||
className="text-xs text-neutral-500">{result.os.version}</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="space-x-1">
|
|
||||||
<span>{result.browser.name}</span>
|
|
||||||
<span
|
|
||||||
className="text-xs text-neutral-500">{result.browser.version}</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
{new Date(session.authenticated_at!).toLocaleString()}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
</TableHeader>
|
||||||
})
|
<TableBody>
|
||||||
|
{
|
||||||
|
detailIdentitySessions.map((session) => {
|
||||||
|
|
||||||
|
const device = session.devices![0];
|
||||||
|
const parser = new UAParser(device.user_agent);
|
||||||
|
const result = parser.getResult();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow key={session.id}>
|
||||||
|
<TableCell className="space-x-1">
|
||||||
|
<span>{result.os.name}</span>
|
||||||
|
<span
|
||||||
|
className="text-xs text-neutral-500">{result.os.version}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="space-x-1">
|
||||||
|
<span>{result.browser.name}</span>
|
||||||
|
<span
|
||||||
|
className="text-xs text-neutral-500">{result.browser.version}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(session.authenticated_at!).toLocaleString()}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
:
|
:
|
||||||
<ErrorDisplay title="No sessions" message=""/>
|
<p>This user has no active sessions</p>
|
||||||
}
|
}
|
||||||
</TableBody>
|
</CardContent>
|
||||||
</Table>
|
</Card>
|
||||||
</CardContent>
|
:
|
||||||
</Card>
|
<InsufficientPermission
|
||||||
|
permission={permission.user.session}
|
||||||
|
relation={relation.access}
|
||||||
|
identityId={identityId}/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,9 +33,15 @@ interface IdentityDataTableProps {
|
||||||
data: Identity[];
|
data: Identity[];
|
||||||
page: number;
|
page: number;
|
||||||
query: string;
|
query: string;
|
||||||
|
permission: {
|
||||||
|
pmEditUser: boolean;
|
||||||
|
pmDeleteUser: boolean;
|
||||||
|
pmEditUserState: boolean;
|
||||||
|
pmDeleteUserSession: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IdentityDataTable({ data, page, query }: IdentityDataTableProps) {
|
export function IdentityDataTable({ data, page, query, permission }: IdentityDataTableProps) {
|
||||||
|
|
||||||
const columns: ColumnDef<Identity>[] = [
|
const columns: ColumnDef<Identity>[] = [
|
||||||
{
|
{
|
||||||
|
@ -137,6 +143,7 @@ export function IdentityDataTable({ data, page, query }: IdentityDataTableProps)
|
||||||
setCurrentIdentity(identity);
|
setCurrentIdentity(identity);
|
||||||
setIdentitySessionVisible(true);
|
setIdentitySessionVisible(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={!permission.pmDeleteUserSession}
|
||||||
className="flex items-center space-x-2 text-red-500">
|
className="flex items-center space-x-2 text-red-500">
|
||||||
<UserMinus className="h-4 w-4"/>
|
<UserMinus className="h-4 w-4"/>
|
||||||
<span>Delete sessions</span>
|
<span>Delete sessions</span>
|
||||||
|
@ -148,6 +155,7 @@ export function IdentityDataTable({ data, page, query }: IdentityDataTableProps)
|
||||||
setCurrentIdentity(identity);
|
setCurrentIdentity(identity);
|
||||||
setBlockIdentityVisible(true);
|
setBlockIdentityVisible(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={!permission.pmEditUserState}
|
||||||
className="flex items-center space-x-2 text-red-500">
|
className="flex items-center space-x-2 text-red-500">
|
||||||
<UserX className="h-4 w-4"/>
|
<UserX className="h-4 w-4"/>
|
||||||
<span>Block identity</span>
|
<span>Block identity</span>
|
||||||
|
@ -160,6 +168,7 @@ export function IdentityDataTable({ data, page, query }: IdentityDataTableProps)
|
||||||
setCurrentIdentity(identity);
|
setCurrentIdentity(identity);
|
||||||
setUnblockIdentityVisible(true);
|
setUnblockIdentityVisible(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={!permission.pmEditUserState}
|
||||||
className="flex items-center space-x-2 text-red-500">
|
className="flex items-center space-x-2 text-red-500">
|
||||||
<UserCheck className="h-4 w-4"/>
|
<UserCheck className="h-4 w-4"/>
|
||||||
<span>Unblock identity</span>
|
<span>Unblock identity</span>
|
||||||
|
@ -170,6 +179,7 @@ export function IdentityDataTable({ data, page, query }: IdentityDataTableProps)
|
||||||
setCurrentIdentity(identity);
|
setCurrentIdentity(identity);
|
||||||
setDeleteIdentityVisible(true);
|
setDeleteIdentityVisible(true);
|
||||||
}}
|
}}
|
||||||
|
disabled={!permission.pmDeleteUser}
|
||||||
className="flex items-center space-x-2 text-red-500">
|
className="flex items-center space-x-2 text-red-500">
|
||||||
<Trash className="h-4 w-4"/>
|
<Trash className="h-4 w-4"/>
|
||||||
<span>Delete identity</span>
|
<span>Delete identity</span>
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { IdentityDataTable } from '@/app/(inside)/user/data-table';
|
||||||
import { SearchInput } from '@/components/search-input';
|
import { SearchInput } from '@/components/search-input';
|
||||||
import { queryIdentities } from '@/lib/action/identity';
|
import { queryIdentities } from '@/lib/action/identity';
|
||||||
import { IdentityPagination } from '@/components/pagination';
|
import { IdentityPagination } from '@/components/pagination';
|
||||||
|
import { checkPermission, requirePermission, requireSession } from '@/lib/action/authentication';
|
||||||
|
import InsufficientPermission from '@/components/insufficient-permission';
|
||||||
|
import { permission, relation } from '@/lib/permission';
|
||||||
|
|
||||||
export default async function UserPage(
|
export default async function UserPage(
|
||||||
{
|
{
|
||||||
|
@ -12,6 +15,17 @@ export default async function UserPage(
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const identityId = session.identity!.id;
|
||||||
|
|
||||||
|
await requirePermission(permission.stack.dashboard, relation.access, identityId);
|
||||||
|
|
||||||
|
const pmAccessUser = await checkPermission(permission.user.it, relation.access, identityId);
|
||||||
|
const pmEditUser = await checkPermission(permission.user.it, relation.edit, identityId);
|
||||||
|
const pmDeleteUser = await checkPermission(permission.user.it, relation.delete, identityId);
|
||||||
|
const pmEditUserState = await checkPermission(permission.user.state, relation.edit, identityId);
|
||||||
|
const pmDeleteUserSession = await checkPermission(permission.user.session, relation.delete, identityId);
|
||||||
|
|
||||||
const params = await searchParams;
|
const params = await searchParams;
|
||||||
|
|
||||||
const page = params.page ? Number(params.page) : 1;
|
const page = params.page ? Number(params.page) : 1;
|
||||||
|
@ -20,7 +34,7 @@ export default async function UserPage(
|
||||||
let pageSize = 50;
|
let pageSize = 50;
|
||||||
let paginationRange = 11;
|
let paginationRange = 11;
|
||||||
|
|
||||||
const { data, itemCount, pageCount } = await queryIdentities({ page, pageSize, query });
|
const users = pmAccessUser && await queryIdentities(page, pageSize, query);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
@ -31,23 +45,45 @@ export default async function UserPage(
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<SearchInput
|
{
|
||||||
value={query}
|
!pmAccessUser && (
|
||||||
pageParamKey="page"
|
<InsufficientPermission
|
||||||
queryParamKey="query"
|
permission={permission.user.it}
|
||||||
placeholder="Search for addresses and traits"/>
|
relation={relation.access}
|
||||||
<div>
|
identityId={identityId}
|
||||||
<p className="text-xs text-neutral-500">{itemCount} item{itemCount && itemCount > 1 ? 's' : ''} found</p>
|
/>
|
||||||
<IdentityDataTable
|
)
|
||||||
data={data}
|
}
|
||||||
page={page}
|
{
|
||||||
query={query}/>
|
pmAccessUser && users && (
|
||||||
</div>
|
<>
|
||||||
<IdentityPagination
|
<SearchInput
|
||||||
page={page}
|
value={query}
|
||||||
pageCount={pageCount}
|
pageParamKey="page"
|
||||||
pageParamKey="page"
|
queryParamKey="query"
|
||||||
paginationRange={paginationRange}/>
|
placeholder="Search for addresses and traits"/>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-neutral-500">{users.itemCount} item{users.itemCount && users.itemCount > 1 ? 's' : ''} found</p>
|
||||||
|
<IdentityDataTable
|
||||||
|
data={users.data}
|
||||||
|
page={page}
|
||||||
|
query={query}
|
||||||
|
permission={{
|
||||||
|
pmEditUser: pmEditUser,
|
||||||
|
pmDeleteUser: pmDeleteUser,
|
||||||
|
pmEditUserState: pmEditUserState,
|
||||||
|
pmDeleteUserSession: pmDeleteUserSession,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<IdentityPagination
|
||||||
|
page={page}
|
||||||
|
pageCount={users.pageCount}
|
||||||
|
pageParamKey="page"
|
||||||
|
paginationRange={paginationRange}/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,6 +15,7 @@ interface DynamicFormProps<T extends FieldValues> {
|
||||||
onValid: SubmitHandler<T>,
|
onValid: SubmitHandler<T>,
|
||||||
onInvalid: SubmitErrorHandler<T>,
|
onInvalid: SubmitErrorHandler<T>,
|
||||||
submitLabel?: string,
|
submitLabel?: string,
|
||||||
|
disabled?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DynamicForm<T extends FieldValues>(
|
export function DynamicForm<T extends FieldValues>(
|
||||||
|
@ -25,6 +26,7 @@ export function DynamicForm<T extends FieldValues>(
|
||||||
onValid,
|
onValid,
|
||||||
onInvalid,
|
onInvalid,
|
||||||
submitLabel,
|
submitLabel,
|
||||||
|
disabled,
|
||||||
}: DynamicFormProps<T>,
|
}: DynamicFormProps<T>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ export function DynamicForm<T extends FieldValues>(
|
||||||
key={fullFieldName}
|
key={fullFieldName}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex items-center space-x-2 space-y-0">
|
<FormItem className="flex items-center space-x-2 space-y-0">
|
||||||
<Checkbox {...field} checked={field.value}/>
|
<Checkbox {...field} disabled={disabled} checked={field.value}/>
|
||||||
<FormLabel>{key}</FormLabel>
|
<FormLabel>{key}</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
@ -65,7 +67,7 @@ export function DynamicForm<T extends FieldValues>(
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{value.title}</FormLabel>
|
<FormLabel>{value.title}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder={value.title} {...field} />
|
<Input placeholder={value.title} {...field} disabled={disabled}/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>{value.description}</FormDescription>
|
<FormDescription>{value.description}</FormDescription>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -87,7 +89,7 @@ export function DynamicForm<T extends FieldValues>(
|
||||||
<Button
|
<Button
|
||||||
key="submit"
|
key="submit"
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!form.formState.isDirty}
|
disabled={!form.formState.isDirty || disabled}
|
||||||
>
|
>
|
||||||
{submitLabel ?? 'Submit'}
|
{submitLabel ?? 'Submit'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -28,12 +28,21 @@ import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
interface IdentityActionProps {
|
interface IdentityActionProps {
|
||||||
identity: Identity;
|
identity: Identity,
|
||||||
|
permissions: {
|
||||||
|
pmDeleteUser: boolean;
|
||||||
|
pmEditUserState: boolean;
|
||||||
|
pmDeleteUserSession: boolean;
|
||||||
|
pmCreateUserCode: boolean;
|
||||||
|
pmCreateUserLink: boolean;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IdentityActions({ identity }: IdentityActionProps,
|
export function IdentityActions({ identity, permissions }: IdentityActionProps,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
console.log('IdentityActions', 'Permissions', permissions);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const [dialogVisible, setDialogVisible] = useState(false);
|
const [dialogVisible, setDialogVisible] = useState(false);
|
||||||
|
@ -122,7 +131,10 @@ export function IdentityActions({ identity }: IdentityActionProps,
|
||||||
dialogDescription="Are you sure you want to create a recovery code for this identity?"
|
dialogDescription="Are you sure you want to create a recovery code for this identity?"
|
||||||
dialogButtonSubmit="Create code"
|
dialogButtonSubmit="Create code"
|
||||||
>
|
>
|
||||||
<Button className="mr-2" size="icon">
|
<Button
|
||||||
|
disabled={!permissions.pmCreateUserCode}
|
||||||
|
className="mr-2"
|
||||||
|
size="icon">
|
||||||
<Key className="h-4"/>
|
<Key className="h-4"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ConfirmationDialogWrapper>
|
</ConfirmationDialogWrapper>
|
||||||
|
@ -142,7 +154,10 @@ export function IdentityActions({ identity }: IdentityActionProps,
|
||||||
dialogDescription="Are you sure you want to create a recovery link for this identity?"
|
dialogDescription="Are you sure you want to create a recovery link for this identity?"
|
||||||
dialogButtonSubmit="Create link"
|
dialogButtonSubmit="Create link"
|
||||||
>
|
>
|
||||||
<Button className="mr-2" size="icon">
|
<Button
|
||||||
|
disabled={!permissions.pmCreateUserLink}
|
||||||
|
className="mr-2"
|
||||||
|
size="icon">
|
||||||
<Link className="h-4"/>
|
<Link className="h-4"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ConfirmationDialogWrapper>
|
</ConfirmationDialogWrapper>
|
||||||
|
@ -160,7 +175,10 @@ export function IdentityActions({ identity }: IdentityActionProps,
|
||||||
dialogDescription="Are you sure you want to deactivate this identity? The user will not be able to sign-in or use any active session until re-activation!"
|
dialogDescription="Are you sure you want to deactivate this identity? The user will not be able to sign-in or use any active session until re-activation!"
|
||||||
dialogButtonSubmit="Deactivate"
|
dialogButtonSubmit="Deactivate"
|
||||||
>
|
>
|
||||||
<Button className="mr-2" size="icon">
|
<Button
|
||||||
|
disabled={!permissions.pmEditUserState}
|
||||||
|
className="mr-2"
|
||||||
|
size="icon">
|
||||||
<UserX className="h-4"/>
|
<UserX className="h-4"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ConfirmationDialogWrapper>
|
</ConfirmationDialogWrapper>
|
||||||
|
@ -176,7 +194,10 @@ export function IdentityActions({ identity }: IdentityActionProps,
|
||||||
dialogDescription="Are you sure you want to activate this identity?"
|
dialogDescription="Are you sure you want to activate this identity?"
|
||||||
dialogButtonSubmit="Activate"
|
dialogButtonSubmit="Activate"
|
||||||
>
|
>
|
||||||
<Button className="mr-2" size="icon">
|
<Button
|
||||||
|
disabled={!permissions.pmEditUserState}
|
||||||
|
className="mr-2"
|
||||||
|
size="icon">
|
||||||
<UserCheck className="h-4"/>
|
<UserCheck className="h-4"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ConfirmationDialogWrapper>
|
</ConfirmationDialogWrapper>
|
||||||
|
@ -194,7 +215,10 @@ export function IdentityActions({ identity }: IdentityActionProps,
|
||||||
dialogButtonSubmit="Invalidate sessions"
|
dialogButtonSubmit="Invalidate sessions"
|
||||||
dialogButtonSubmitProps={{ variant: 'destructive' }}
|
dialogButtonSubmitProps={{ variant: 'destructive' }}
|
||||||
>
|
>
|
||||||
<Button className="mr-2" size="icon">
|
<Button
|
||||||
|
disabled={!permissions.pmDeleteUserSession}
|
||||||
|
className="mr-2"
|
||||||
|
size="icon">
|
||||||
<UserMinus className="h-4"/>
|
<UserMinus className="h-4"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ConfirmationDialogWrapper>
|
</ConfirmationDialogWrapper>
|
||||||
|
@ -214,7 +238,10 @@ export function IdentityActions({ identity }: IdentityActionProps,
|
||||||
dialogButtonSubmit="Delete identity"
|
dialogButtonSubmit="Delete identity"
|
||||||
dialogButtonSubmitProps={{ variant: 'destructive' }}
|
dialogButtonSubmitProps={{ variant: 'destructive' }}
|
||||||
>
|
>
|
||||||
<Button className="mr-2" size="icon">
|
<Button
|
||||||
|
disabled={!permissions.pmDeleteUser}
|
||||||
|
className="mr-2"
|
||||||
|
size="icon">
|
||||||
<Trash className="h-4"/>
|
<Trash className="h-4"/>
|
||||||
</Button>
|
</Button>
|
||||||
</ConfirmationDialogWrapper>
|
</ConfirmationDialogWrapper>
|
||||||
|
|
|
@ -36,7 +36,7 @@ export function IdentityCredentials({ identity }: IdentityCredentialsProps) {
|
||||||
(
|
(
|
||||||
<ConfirmationDialogWrapper
|
<ConfirmationDialogWrapper
|
||||||
onSubmit={async () => {
|
onSubmit={async () => {
|
||||||
deleteIdentityCredential({ id: identity.id, type: key as never })
|
deleteIdentityCredential(identity.id, key as never)
|
||||||
.then(() => toast.success(`Credential ${key} deleted`))
|
.then(() => toast.success(`Credential ${key} deleted`))
|
||||||
.catch(() => toast.error(`Deleting credential ${key} failed`));
|
.catch(() => toast.error(`Deleting credential ${key} failed`));
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -16,9 +16,10 @@ import { useState } from 'react';
|
||||||
interface IdentityTraitFormProps {
|
interface IdentityTraitFormProps {
|
||||||
schema: KratosSchema;
|
schema: KratosSchema;
|
||||||
identity: Identity;
|
identity: Identity;
|
||||||
|
disabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IdentityTraits({ schema, identity }: IdentityTraitFormProps) {
|
export function IdentityTraits({ schema, identity, disabled }: IdentityTraitFormProps) {
|
||||||
|
|
||||||
const [currentIdentity, setCurrentIdentity] = useState(identity);
|
const [currentIdentity, setCurrentIdentity] = useState(identity);
|
||||||
|
|
||||||
|
@ -47,16 +48,16 @@ export function IdentityTraits({ schema, identity }: IdentityTraitFormProps) {
|
||||||
delete traits['metadata_public'];
|
delete traits['metadata_public'];
|
||||||
delete traits['metadata_admin'];
|
delete traits['metadata_admin'];
|
||||||
|
|
||||||
updateIdentity({
|
updateIdentity(
|
||||||
id: currentIdentity.id,
|
currentIdentity.id,
|
||||||
body: {
|
{
|
||||||
schema_id: currentIdentity.schema_id,
|
schema_id: currentIdentity.schema_id,
|
||||||
state: currentIdentity.state!,
|
state: currentIdentity.state!,
|
||||||
traits: traits,
|
traits: traits,
|
||||||
metadata_public: data.metadata_public,
|
metadata_public: data.metadata_public,
|
||||||
metadata_admin: data.metadata_admin,
|
metadata_admin: data.metadata_admin,
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
.then((identity) => {
|
.then((identity) => {
|
||||||
setCurrentIdentity(identity);
|
setCurrentIdentity(identity);
|
||||||
toast.success('Identity updated');
|
toast.success('Identity updated');
|
||||||
|
@ -74,10 +75,11 @@ export function IdentityTraits({ schema, identity }: IdentityTraitFormProps) {
|
||||||
return (
|
return (
|
||||||
<DynamicForm
|
<DynamicForm
|
||||||
form={form}
|
form={form}
|
||||||
|
disabled={disabled}
|
||||||
properties={schema.properties.traits.properties}
|
properties={schema.properties.traits.properties}
|
||||||
onValid={onValid}
|
onValid={onValid}
|
||||||
onInvalid={onInvalid}
|
onInvalid={onInvalid}
|
||||||
submitLabel="Update Identity"
|
submitLabel={disabled ? 'Insufficient permissions' : 'Update Identity'}
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
{...form.register('metadata_public')}
|
{...form.register('metadata_public')}
|
||||||
|
@ -86,7 +88,7 @@ export function IdentityTraits({ schema, identity }: IdentityTraitFormProps) {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Public Metadata</FormLabel>
|
<FormLabel>Public Metadata</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea placeholder="Public Metadata" {...field} />
|
<Textarea placeholder="Public Metadata" {...field} disabled={disabled}/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>This has to be valid JSON</FormDescription>
|
<FormDescription>This has to be valid JSON</FormDescription>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -99,7 +101,7 @@ export function IdentityTraits({ schema, identity }: IdentityTraitFormProps) {
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Admin Metadata</FormLabel>
|
<FormLabel>Admin Metadata</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea placeholder="Admin Metadata" {...field} />
|
<Textarea placeholder="Admin Metadata" {...field} disabled={disabled}/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>This has to be valid JSON</FormDescription>
|
<FormDescription>This has to be valid JSON</FormDescription>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
36
dashboard/src/components/insufficient-permission.tsx
Normal file
36
dashboard/src/components/insufficient-permission.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
|
||||||
|
interface InsufficientPermissionProps {
|
||||||
|
permission: string;
|
||||||
|
relation: string;
|
||||||
|
identityId: string;
|
||||||
|
classNames?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function InsufficientPermission(
|
||||||
|
{
|
||||||
|
permission,
|
||||||
|
relation,
|
||||||
|
identityId,
|
||||||
|
classNames,
|
||||||
|
}: InsufficientPermissionProps,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Card className={classNames}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Insufficient Permission</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
You are missing the permission to see this content.<br/>
|
||||||
|
If you think this is an error, please contact your system administrator
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter>
|
||||||
|
<CardDescription className="text-xs">
|
||||||
|
Permission: {permission}<br/>
|
||||||
|
Relation: {relation}<br/>
|
||||||
|
Identity: {identityId}
|
||||||
|
</CardDescription>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ export function StatusCard({ title, version, name, status, className }: StatusCa
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{
|
{
|
||||||
status.errors.map((error) => <span>{error}</span>)
|
status.errors.map((error) => <span key={error}>{error}</span>)
|
||||||
}
|
}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
106
dashboard/src/lib/action/authentication.ts
Normal file
106
dashboard/src/lib/action/authentication.ts
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { getFrontendApi, getPermissionApi } from '@/ory/sdk/server';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
export async function getSession() {
|
||||||
|
|
||||||
|
const cookie = await cookies();
|
||||||
|
|
||||||
|
const frontendApi = await getFrontendApi();
|
||||||
|
|
||||||
|
return frontendApi
|
||||||
|
.toSession({ cookie: 'ory_kratos_session=' + cookie.get('ory_kratos_session')?.value })
|
||||||
|
.then((response) => response.data)
|
||||||
|
.catch(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requireSession() {
|
||||||
|
|
||||||
|
const session = await getSession();
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
|
||||||
|
// TODO: set return_to dynamically
|
||||||
|
const url = process.env.NEXT_PUBLIC_AUTHENTICATION_NODE_URL +
|
||||||
|
'/flow/login?return_to=' + process.env.NEXT_PUBLIC_DASHBOARD_NODE_URL;
|
||||||
|
|
||||||
|
console.log('Intercepted request with missing session');
|
||||||
|
console.log('Redirecting client to: ', url);
|
||||||
|
|
||||||
|
redirect(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function checkRole(
|
||||||
|
object: string,
|
||||||
|
subjectId: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const permissionApi = await getPermissionApi();
|
||||||
|
return permissionApi.checkPermission({
|
||||||
|
namespace: 'roles',
|
||||||
|
object,
|
||||||
|
relation: 'member',
|
||||||
|
subjectId,
|
||||||
|
})
|
||||||
|
.then(({ data: { allowed } }) => allowed)
|
||||||
|
.catch(_ => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requireRole(
|
||||||
|
object: string,
|
||||||
|
subjectId: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const hasRole = await checkRole(object, subjectId);
|
||||||
|
|
||||||
|
if (!hasRole) {
|
||||||
|
console.log(`Intercepted request with missing role ${object} for identity ${subjectId}`);
|
||||||
|
redirect('/unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function checkPermission(
|
||||||
|
object: string,
|
||||||
|
relation: string,
|
||||||
|
subjectId: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const permissionApi = await getPermissionApi();
|
||||||
|
return permissionApi.checkPermission({
|
||||||
|
namespace: 'permissions',
|
||||||
|
object,
|
||||||
|
relation,
|
||||||
|
subjectId,
|
||||||
|
})
|
||||||
|
.then(({ data: { allowed } }) => allowed)
|
||||||
|
.catch(_ => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requirePermission(
|
||||||
|
object: string,
|
||||||
|
relation: string,
|
||||||
|
subjectId: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const allowed = await checkPermission(
|
||||||
|
object,
|
||||||
|
relation,
|
||||||
|
subjectId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allowed) {
|
||||||
|
console.log(`Intercepted request with insufficient permission (${object}#${relation}@${subjectId})`);
|
||||||
|
redirect('/unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
|
return allowed;
|
||||||
|
}
|
|
@ -12,14 +12,43 @@ import {
|
||||||
import { getDB } from '@/db';
|
import { getDB } from '@/db';
|
||||||
import { identities, identity_recovery_addresses, identity_verifiable_addresses } from '@/db/schema';
|
import { identities, identity_recovery_addresses, identity_verifiable_addresses } from '@/db/schema';
|
||||||
import { eq, ilike, or, sql } from 'drizzle-orm';
|
import { eq, ilike, or, sql } from 'drizzle-orm';
|
||||||
|
import { checkPermission, requireSession } from '@/lib/action/authentication';
|
||||||
|
import { permission, relation } from '@/lib/permission';
|
||||||
|
|
||||||
interface QueryIdentitiesProps {
|
export async function getIdentity(id: string) {
|
||||||
page: number,
|
|
||||||
pageSize: number,
|
const session = await requireSession();
|
||||||
query?: string,
|
const allowed = await checkPermission(permission.user.it, relation.access, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityApi = await getIdentityApi();
|
||||||
|
const { data } = await identityApi.getIdentity({ id });
|
||||||
|
|
||||||
|
console.log('Got identity', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryIdentities({ page, pageSize, query }: QueryIdentitiesProps) {
|
|
||||||
|
export async function getIdentitySchema(id: string) {
|
||||||
|
|
||||||
|
const identityApi = await getIdentityApi();
|
||||||
|
const { data } = await identityApi.getIdentitySchema({ id: id });
|
||||||
|
|
||||||
|
console.log('Got identity schema');
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function queryIdentities(page: number, pageSize: number, query?: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.it, relation.access, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
if (page < 1 || pageSize < 1) {
|
if (page < 1 || pageSize < 1) {
|
||||||
return {
|
return {
|
||||||
|
@ -73,13 +102,13 @@ export async function queryIdentities({ page, pageSize, query }: QueryIdentities
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateIdentity(id: string, body: UpdateIdentityBody) {
|
||||||
|
|
||||||
interface UpdatedIdentityProps {
|
const session = await requireSession();
|
||||||
id: string;
|
const allowed = await checkPermission(permission.user.it, relation.edit, session.identity!.id);
|
||||||
body: UpdateIdentityBody;
|
if (!allowed) {
|
||||||
}
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
export async function updateIdentity({ id, body }: UpdatedIdentityProps) {
|
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.updateIdentity({
|
const { data } = await identityApi.updateIdentity({
|
||||||
|
@ -94,12 +123,13 @@ export async function updateIdentity({ id, body }: UpdatedIdentityProps) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DeleteIdentityCredentialProps {
|
export async function deleteIdentityCredential(id: string, type: DeleteIdentityCredentialsTypeEnum) {
|
||||||
id: string;
|
|
||||||
type: DeleteIdentityCredentialsTypeEnum;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteIdentityCredential({ id, type }: DeleteIdentityCredentialProps) {
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.credential, relation.delete, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.deleteIdentityCredentials({ id, type });
|
const { data } = await identityApi.deleteIdentityCredentials({ id, type });
|
||||||
|
@ -111,8 +141,30 @@ export async function deleteIdentityCredential({ id, type }: DeleteIdentityCrede
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listIdentitySessions(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.session, relation.access, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
|
const identityApi = await getIdentityApi();
|
||||||
|
const { data } = await identityApi.listIdentitySessions({ id });
|
||||||
|
|
||||||
|
console.log('Listed identity\'s sessions', data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteIdentitySessions(id: string) {
|
export async function deleteIdentitySessions(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.session, relation.delete, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.deleteIdentitySessions({ id });
|
const { data } = await identityApi.deleteIdentitySessions({ id });
|
||||||
|
|
||||||
|
@ -125,6 +177,12 @@ export async function deleteIdentitySessions(id: string) {
|
||||||
|
|
||||||
export async function createRecoveryCode(id: string) {
|
export async function createRecoveryCode(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.code, relation.create, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.createRecoveryCodeForIdentity({
|
const { data } = await identityApi.createRecoveryCodeForIdentity({
|
||||||
createRecoveryCodeForIdentityBody: {
|
createRecoveryCodeForIdentityBody: {
|
||||||
|
@ -139,6 +197,12 @@ export async function createRecoveryCode(id: string) {
|
||||||
|
|
||||||
export async function createRecoveryLink(id: string) {
|
export async function createRecoveryLink(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.link, relation.create, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.createRecoveryLinkForIdentity({
|
const { data } = await identityApi.createRecoveryLinkForIdentity({
|
||||||
createRecoveryLinkForIdentityBody: {
|
createRecoveryLinkForIdentityBody: {
|
||||||
|
@ -153,6 +217,12 @@ export async function createRecoveryLink(id: string) {
|
||||||
|
|
||||||
export async function blockIdentity(id: string) {
|
export async function blockIdentity(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.state, relation.edit, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.patchIdentity({
|
const { data } = await identityApi.patchIdentity({
|
||||||
id,
|
id,
|
||||||
|
@ -172,6 +242,12 @@ export async function blockIdentity(id: string) {
|
||||||
|
|
||||||
export async function unblockIdentity(id: string) {
|
export async function unblockIdentity(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.state, relation.edit, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.patchIdentity({
|
const { data } = await identityApi.patchIdentity({
|
||||||
id,
|
id,
|
||||||
|
@ -191,6 +267,12 @@ export async function unblockIdentity(id: string) {
|
||||||
|
|
||||||
export async function deleteIdentity(id: string) {
|
export async function deleteIdentity(id: string) {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.user.it, relation.delete, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
throw Error('Unauthorised');
|
||||||
|
}
|
||||||
|
|
||||||
const identityApi = await getIdentityApi();
|
const identityApi = await getIdentityApi();
|
||||||
const { data } = await identityApi.deleteIdentity({ id });
|
const { data } = await identityApi.deleteIdentity({ id });
|
||||||
|
|
||||||
|
|
84
dashboard/src/lib/action/metadata.ts
Normal file
84
dashboard/src/lib/action/metadata.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { getHydraMetadataApi, getKetoMetadataApi, getKratosMetadataApi } from '@/ory/sdk/server';
|
||||||
|
import { MetadataApiReady } from '@/components/status-card';
|
||||||
|
import { checkPermission, requireSession } from '@/lib/action/authentication';
|
||||||
|
import { permission, relation } from '@/lib/permission';
|
||||||
|
|
||||||
|
export async function kratosMetadata() {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.stack.status, relation.access, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = await getKratosMetadataApi();
|
||||||
|
|
||||||
|
const version = await api.getVersion()
|
||||||
|
.then(res => res.data.version)
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
const status = await fetch(process.env.ORY_KRATOS_ADMIN_URL + '/health/ready')
|
||||||
|
.then((response) => response.json() as MetadataApiReady)
|
||||||
|
.catch(() => {
|
||||||
|
return { errors: ['No instance running'] } as MetadataApiReady;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hydraMetadata() {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.stack.status, relation.access, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = await getHydraMetadataApi();
|
||||||
|
|
||||||
|
const version = await api.getVersion()
|
||||||
|
.then(res => res.data.version)
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
const status = await fetch(process.env.ORY_HYDRA_ADMIN_URL + '/health/ready')
|
||||||
|
.then((response) => response.json() as MetadataApiReady)
|
||||||
|
.catch(() => {
|
||||||
|
return { errors: ['No instance running'] } as MetadataApiReady;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ketoMetadata() {
|
||||||
|
|
||||||
|
const session = await requireSession();
|
||||||
|
const allowed = await checkPermission(permission.stack.status, relation.access, session.identity!.id);
|
||||||
|
if (!allowed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = await getKetoMetadataApi();
|
||||||
|
|
||||||
|
const version = await api.getVersion()
|
||||||
|
.then(res => res.data.version)
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
const status = await fetch(process.env.ORY_KETO_ADMIN_URL + '/health/ready')
|
||||||
|
.then((response) => response.json() as MetadataApiReady)
|
||||||
|
.catch(() => {
|
||||||
|
return { errors: ['No instance running'] } as MetadataApiReady;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
}
|
23
dashboard/src/lib/permission.ts
Normal file
23
dashboard/src/lib/permission.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
export const permission = {
|
||||||
|
stack: {
|
||||||
|
dashboard: 'admin.stack.dashboard',
|
||||||
|
status: 'admin.stack.status',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
it: 'admin.user',
|
||||||
|
address: 'admin.user.address',
|
||||||
|
code: 'admin.user.code',
|
||||||
|
credential: 'admin.user.credential',
|
||||||
|
link: 'admin.user.link',
|
||||||
|
session: 'admin.user.session',
|
||||||
|
state: 'admin.user.state',
|
||||||
|
trait: 'admin.user.trait',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const relation = {
|
||||||
|
access: 'access',
|
||||||
|
create: 'create',
|
||||||
|
edit: 'edit',
|
||||||
|
delete: 'delete',
|
||||||
|
};
|
|
@ -1,47 +1,29 @@
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { cookies } from 'next/headers';
|
import { checkPermission, getSession } from '@/lib/action/authentication';
|
||||||
import { getFrontendApi, getPermissionApi } from '@/ory/sdk/server';
|
import { permission, relation } from '@/lib/permission';
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
|
|
||||||
const frontendApi = await getFrontendApi();
|
// middleware can not work with requireSession, requireRole and
|
||||||
const cookie = await cookies();
|
// requirePermission due to the different redirect mechanisms in use!
|
||||||
|
|
||||||
const session = await frontendApi
|
|
||||||
.toSession({ cookie: 'ory_kratos_session=' + cookie.get('ory_kratos_session')?.value })
|
|
||||||
.then((response) => response.data)
|
|
||||||
.catch(() => null);
|
|
||||||
|
|
||||||
|
const session = await getSession();
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|
||||||
console.log('NO SESSION');
|
console.log('middleware', 'MISSING SESSION');
|
||||||
|
|
||||||
const url = process.env.NEXT_PUBLIC_AUTHENTICATION_NODE_URL +
|
const url = process.env.NEXT_PUBLIC_AUTHENTICATION_NODE_URL +
|
||||||
'/flow/login?return_to=' +
|
'/flow/login?return_to=' +
|
||||||
process.env.NEXT_PUBLIC_DASHBOARD_NODE_URL;
|
process.env.NEXT_PUBLIC_DASHBOARD_NODE_URL;
|
||||||
|
|
||||||
console.log('REDIRECT TO', url);
|
console.log('middleware', 'REDIRECT TO', url);
|
||||||
|
return NextResponse.redirect(url!);
|
||||||
return NextResponse.redirect(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionApi = await getPermissionApi();
|
const allowed = await checkPermission(permission.stack.dashboard, relation.access, session.identity!.id);
|
||||||
const isAdmin = await permissionApi.checkPermission({
|
|
||||||
namespace: 'roles',
|
|
||||||
object: 'admin',
|
|
||||||
relation: 'member',
|
|
||||||
subjectId: session!.identity!.id,
|
|
||||||
})
|
|
||||||
.then(({ data: { allowed } }) => {
|
|
||||||
console.log('is_admin', session!.identity!.id, allowed);
|
|
||||||
return allowed;
|
|
||||||
})
|
|
||||||
.catch((response) => {
|
|
||||||
console.log('is_admin', session!.identity!.id, response, 'check failed');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isAdmin) {
|
|
||||||
|
if (allowed) {
|
||||||
if (request.nextUrl.pathname === '/unauthorised') {
|
if (request.nextUrl.pathname === '/unauthorised') {
|
||||||
return redirect('/', 'HAS PERMISSION BUT ACCESSING /unauthorized');
|
return redirect('/', 'HAS PERMISSION BUT ACCESSING /unauthorized');
|
||||||
}
|
}
|
||||||
|
@ -55,9 +37,9 @@ export async function middleware(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirect(path: string, reason: string) {
|
function redirect(path: string, reason: string) {
|
||||||
console.log(reason);
|
console.log('middleware', reason);
|
||||||
const url = `${process.env.NEXT_PUBLIC_DASHBOARD_NODE_URL}${path}`;
|
const url = `${process.env.NEXT_PUBLIC_DASHBOARD_NODE_URL}${path}`;
|
||||||
console.log('REDIRECT TO', url);
|
console.log('middleware', 'REDIRECT TO', url);
|
||||||
return NextResponse.redirect(url!);
|
return NextResponse.redirect(url!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# this script adds a new oath client using the
|
# this script adds a new OAuth client using the
|
||||||
# Ory Hydra CLI and writes the client id and
|
# Ory Hydra CLI and writes the client id and
|
||||||
# client secret to the command line.
|
# client secret to the command line.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# this script adds a new oath client using the
|
# this script adds a new OAuth client using the
|
||||||
# Ory Hydra CLI and uses the client to start
|
# Ory Hydra CLI and uses the client to start
|
||||||
# the Ory Hydra test application.
|
# the Ory Hydra test application.
|
||||||
|
|
||||||
|
|
30
docker/ory-dev/keto-add-permission-to-role.sh
Normal file
30
docker/ory-dev/keto-add-permission-to-role.sh
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# this script creates a reference from the role to the permission you provide
|
||||||
|
|
||||||
|
# check if a identity id argument was provided
|
||||||
|
if [ $# -ne 4 ]; then
|
||||||
|
echo "Usage: $0 <object> <relation> <role> <role_relation>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set user id variable
|
||||||
|
OBJECT=$1
|
||||||
|
RELATION=$2
|
||||||
|
ROLE=$3
|
||||||
|
ROLE_RELATION=$4
|
||||||
|
|
||||||
|
# execute curl to Ory Keto write endpoint
|
||||||
|
curl --request PUT \
|
||||||
|
--url http://localhost:4467/admin/relation-tuples \
|
||||||
|
--data '{
|
||||||
|
"namespace": "permissions",
|
||||||
|
"object": "'"$OBJECT"'",
|
||||||
|
"relation": "'"$RELATION"'",
|
||||||
|
"subject_set": {
|
||||||
|
"namespace": "roles",
|
||||||
|
"object": "'"$ROLE"'",
|
||||||
|
"relation": "'"$ROLE_RELATION"'"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
|
||||||
|
# write success response to terminal
|
||||||
|
echo "Added relation Permissions:$OBJECT#$RELATION@(Roles:$ROLE#$RELATION)"
|
26
docker/ory-dev/keto-add-permission.sh
Normal file
26
docker/ory-dev/keto-add-permission.sh
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# this script gives the referenced identity the provided permission
|
||||||
|
# make sure to provide the id of the identity
|
||||||
|
|
||||||
|
# check if a required arguments were provided
|
||||||
|
if [ $# -ne 3 ]; then
|
||||||
|
echo "Usage: $0 <object> <relation> <identity_id>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set variables from input
|
||||||
|
OBJECT=$1
|
||||||
|
RELATION=$2
|
||||||
|
IDENTITY_ID=$3
|
||||||
|
|
||||||
|
# execute curl to Ory Keto write endpoint
|
||||||
|
curl --request PUT \
|
||||||
|
--url http://localhost:4467/admin/relation-tuples \
|
||||||
|
--data '{
|
||||||
|
"namespace": "permissions",
|
||||||
|
"object": "'"$OBJECT"'",
|
||||||
|
"relation": "'"$RELATION"'",
|
||||||
|
"subject_id": "'"$IDENTITY_ID"'"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# write success response to terminal
|
||||||
|
echo "Added permission $OBJECT#$RELATION@$IDENTITY_ID"
|
47
docker/ory-dev/keto-init-admin-role.sh
Normal file
47
docker/ory-dev/keto-init-admin-role.sh
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# this script adds all permissions required for full control over the dashboard to
|
||||||
|
# all everybody, who is a member of the admin role
|
||||||
|
|
||||||
|
# Define an array with tuples as strings
|
||||||
|
permissions=(
|
||||||
|
"admin.stack.dashboard#access"
|
||||||
|
"admin.stack.status#access"
|
||||||
|
"admin.user#access"
|
||||||
|
"admin.user#create"
|
||||||
|
"admin.user#edit"
|
||||||
|
"admin.user#delete"
|
||||||
|
"admin.user.session#access"
|
||||||
|
"admin.user.session#delete"
|
||||||
|
"admin.user.state#edit"
|
||||||
|
"admin.user.code#create"
|
||||||
|
"admin.user.link#create"
|
||||||
|
"admin.user.trait#access"
|
||||||
|
"admin.user.trait#edit"
|
||||||
|
"admin.user.address#access"
|
||||||
|
"admin.user.credential#access"
|
||||||
|
"admin.user.credential#delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Iterate over the array
|
||||||
|
for permission in "${permissions[@]}"; do
|
||||||
|
|
||||||
|
# split strings
|
||||||
|
IFS='#' read -r OBJECT RELATION <<< "$permission"
|
||||||
|
|
||||||
|
# execute curl to Ory Keto write endpoint
|
||||||
|
curl --silent --request PUT \
|
||||||
|
--url http://localhost:4467/admin/relation-tuples \
|
||||||
|
--data '{
|
||||||
|
"namespace": "permissions",
|
||||||
|
"object": "'"$OBJECT"'",
|
||||||
|
"relation": "'"$RELATION"'",
|
||||||
|
"subject_set": {
|
||||||
|
"namespace": "roles",
|
||||||
|
"object": "admin",
|
||||||
|
"relation": "member"
|
||||||
|
}
|
||||||
|
}' > /dev/null
|
||||||
|
|
||||||
|
# write success response to terminal
|
||||||
|
echo "Added relation Permissions:$OBJECT#$RELATION@(Roles:admin#member)"
|
||||||
|
|
||||||
|
done
|
|
@ -22,6 +22,8 @@ dsn: postgres://postgres:postgres@ory-postgres:5432/keto?sslmode=disable&max_con
|
||||||
namespaces:
|
namespaces:
|
||||||
- id: 0
|
- id: 0
|
||||||
name: roles
|
name: roles
|
||||||
|
- id: 1
|
||||||
|
name: permissions
|
||||||
|
|
||||||
serve:
|
serve:
|
||||||
read:
|
read:
|
||||||
|
|
Loading…
Add table
Reference in a new issue