refactor: bunch of improvements
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -1,33 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import {
|
||||
IdentityBasic,
|
||||
IdentityOAuth,
|
||||
IdentityToken,
|
||||
} from '@KTXM/MailManager/models/identity'
|
||||
import type { ServiceObject } from '@KTXM/MailManager/models/service'
|
||||
import type { ServiceIdentity } from '@KTXM/MailManager/types/service'
|
||||
import type { ProviderAuthPanelProps, ProviderAuthPanelEmits } from '@KTXM/MailManager/types/integration'
|
||||
import { JmapServiceObject } from '@/models/JmapServiceObject'
|
||||
|
||||
const props = defineProps<ProviderAuthPanelProps>()
|
||||
const emit = defineEmits<ProviderAuthPanelEmits>()
|
||||
|
||||
// Auth method selection
|
||||
const authType = ref<'BA' | 'TA' | 'OA'>('BA')
|
||||
|
||||
// Basic auth state
|
||||
const basicIdentity = ref(props.prefilledIdentity || props.emailAddress || '')
|
||||
const basicSecret = ref(props.prefilledSecret || '')
|
||||
|
||||
// Token auth state
|
||||
const basicIdentity = ref('')
|
||||
const basicSecret = ref('')
|
||||
const bearerToken = ref('')
|
||||
|
||||
// OAuth state
|
||||
const oauthLoading = ref(false)
|
||||
const oauthSuccess = ref(false)
|
||||
const oauthAccessToken = ref('')
|
||||
const oauthRefreshToken = ref('')
|
||||
|
||||
// Validation rules
|
||||
const rules = {
|
||||
required: (value: any) => !!value || 'This field is required'
|
||||
required: (value: unknown) => !!value || 'This field is required'
|
||||
}
|
||||
|
||||
// Validation
|
||||
const isValid = computed(() => {
|
||||
switch (authType.value) {
|
||||
case 'BA':
|
||||
@@ -35,105 +33,183 @@ const isValid = computed(() => {
|
||||
case 'TA':
|
||||
return !!bearerToken.value
|
||||
case 'OA':
|
||||
return oauthSuccess.value
|
||||
return oauthSuccess.value && !!oauthAccessToken.value
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// Build ServiceIdentity object
|
||||
const currentIdentity = computed((): ServiceIdentity | null => {
|
||||
if (!isValid.value) return null
|
||||
|
||||
if (!isValid.value) {
|
||||
return null
|
||||
}
|
||||
|
||||
switch (authType.value) {
|
||||
case 'BA':
|
||||
return {
|
||||
type: 'BA',
|
||||
identity: basicIdentity.value,
|
||||
secret: basicSecret.value
|
||||
secret: basicSecret.value,
|
||||
}
|
||||
case 'TA':
|
||||
return {
|
||||
type: 'TA',
|
||||
token: bearerToken.value
|
||||
token: bearerToken.value,
|
||||
}
|
||||
case 'OA':
|
||||
return {
|
||||
type: 'OA',
|
||||
accessToken: oauthAccessToken.value,
|
||||
refreshToken: oauthRefreshToken.value,
|
||||
refreshToken: oauthRefreshToken.value || undefined,
|
||||
accessScope: ['mail'],
|
||||
accessExpiry: Date.now() + 3600000
|
||||
accessExpiry: Math.floor(Date.now() / 1000) + 3600,
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// Watch and emit changes
|
||||
watch(
|
||||
currentIdentity,
|
||||
(identity) => {
|
||||
if (identity) {
|
||||
emit('update:modelValue', identity)
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
isValid,
|
||||
(valid) => {
|
||||
emit('valid', valid)
|
||||
() => props.service,
|
||||
service => {
|
||||
syncFromService(service)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Update local state if modelValue changes externally
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
authType.value = newValue.type as 'BA' | 'TA' | 'OA'
|
||||
|
||||
switch (newValue.type) {
|
||||
case 'BA':
|
||||
basicIdentity.value = newValue.identity || ''
|
||||
basicSecret.value = newValue.secret || ''
|
||||
break
|
||||
case 'TA':
|
||||
bearerToken.value = newValue.token || ''
|
||||
break
|
||||
case 'OA':
|
||||
oauthAccessToken.value = newValue.accessToken || ''
|
||||
oauthRefreshToken.value = newValue.refreshToken || ''
|
||||
oauthSuccess.value = !!newValue.accessToken
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Prefill identity when email address is provided
|
||||
watch(
|
||||
() => props.emailAddress,
|
||||
(email) => {
|
||||
if (email && !basicIdentity.value) {
|
||||
email => {
|
||||
if (authType.value === 'BA' && email && !basicIdentity.value) {
|
||||
basicIdentity.value = email
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// OAuth flow (stub for now)
|
||||
watch(
|
||||
currentIdentity,
|
||||
identity => {
|
||||
const existingIdentity = props.service?.identity?.toJson() ?? null
|
||||
if (sameIdentity(existingIdentity, identity)) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextService = createServiceObject(props.service)
|
||||
nextService.identity = createIdentityModel(identity)
|
||||
emit('update:service', nextService)
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
function syncFromService(service?: ServiceObject) {
|
||||
const identity = service?.identity?.toJson() ?? null
|
||||
|
||||
if (!identity) {
|
||||
authType.value = 'BA'
|
||||
basicIdentity.value = props.prefilledIdentity || props.emailAddress || ''
|
||||
basicSecret.value = props.prefilledSecret || ''
|
||||
bearerToken.value = ''
|
||||
oauthAccessToken.value = ''
|
||||
oauthRefreshToken.value = ''
|
||||
oauthSuccess.value = false
|
||||
return
|
||||
}
|
||||
|
||||
authType.value = identity.type as 'BA' | 'TA' | 'OA'
|
||||
|
||||
switch (identity.type) {
|
||||
case 'BA':
|
||||
basicIdentity.value = identity.identity || props.prefilledIdentity || props.emailAddress || ''
|
||||
basicSecret.value = identity.secret || props.prefilledSecret || ''
|
||||
bearerToken.value = ''
|
||||
oauthAccessToken.value = ''
|
||||
oauthRefreshToken.value = ''
|
||||
oauthSuccess.value = false
|
||||
break
|
||||
case 'TA':
|
||||
basicIdentity.value = props.prefilledIdentity || props.emailAddress || ''
|
||||
basicSecret.value = props.prefilledSecret || ''
|
||||
bearerToken.value = identity.token || ''
|
||||
oauthAccessToken.value = ''
|
||||
oauthRefreshToken.value = ''
|
||||
oauthSuccess.value = false
|
||||
break
|
||||
case 'OA':
|
||||
basicIdentity.value = props.prefilledIdentity || props.emailAddress || ''
|
||||
basicSecret.value = props.prefilledSecret || ''
|
||||
bearerToken.value = ''
|
||||
oauthAccessToken.value = identity.accessToken || ''
|
||||
oauthRefreshToken.value = identity.refreshToken || ''
|
||||
oauthSuccess.value = !!identity.accessToken
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function createServiceObject(service?: ServiceObject): JmapServiceObject {
|
||||
const nextService = new JmapServiceObject()
|
||||
|
||||
if (service) {
|
||||
nextService.fromJson(service.toJson())
|
||||
}
|
||||
|
||||
return nextService
|
||||
}
|
||||
|
||||
function createIdentityModel(identity: ServiceIdentity | null) {
|
||||
if (identity === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
switch (identity.type) {
|
||||
case 'BA':
|
||||
return new IdentityBasic(identity.identity, identity.secret)
|
||||
case 'TA':
|
||||
return new IdentityToken(identity.token)
|
||||
case 'OA':
|
||||
return new IdentityOAuth(
|
||||
identity.accessToken,
|
||||
identity.accessScope,
|
||||
identity.accessExpiry,
|
||||
identity.refreshToken,
|
||||
identity.refreshLocation
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function sameIdentity(a: ServiceIdentity | null, b: ServiceIdentity | null): boolean {
|
||||
if (a === null || b === null) {
|
||||
return a === b
|
||||
}
|
||||
|
||||
if (a.type !== b.type) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch (a.type) {
|
||||
case 'BA':
|
||||
return b.type === 'BA'
|
||||
&& a.identity === b.identity
|
||||
&& a.secret === b.secret
|
||||
case 'TA':
|
||||
return b.type === 'TA'
|
||||
&& a.token === b.token
|
||||
case 'OA':
|
||||
return b.type === 'OA'
|
||||
&& a.accessToken === b.accessToken
|
||||
&& a.refreshToken === b.refreshToken
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function initiateOAuth() {
|
||||
oauthLoading.value = true
|
||||
|
||||
try {
|
||||
// TODO: Implement OAuth flow when backend is ready
|
||||
emit('error', 'OAuth implementation pending')
|
||||
throw new Error('OAuth implementation pending')
|
||||
} catch (error: any) {
|
||||
emit('error', error.message)
|
||||
} catch (error) {
|
||||
console.warn('[JMAP Auth Panel] OAuth implementation pending', error)
|
||||
} finally {
|
||||
oauthLoading.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user