update
This commit is contained in:
@@ -13,6 +13,7 @@ const props = defineProps<Props>()
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [data: any]
|
submit: [data: any]
|
||||||
|
sso: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { handleSubmit, defineField, errors, meta } = useForm({
|
const { handleSubmit, defineField, errors, meta } = useForm({
|
||||||
@@ -33,6 +34,10 @@ const onSubmit = handleSubmit(async (values) => {
|
|||||||
console.error('Submission failed:', error)
|
console.error('Submission failed:', error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function onSSO() {
|
||||||
|
emit('sso')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -71,4 +76,8 @@ const onSubmit = handleSubmit(async (values) => {
|
|||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<Button @click="onSSO" target="_blank">
|
||||||
|
Login SSO
|
||||||
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import Keycloak from 'keycloak-js'
|
||||||
|
// import { useKeycloak } from '@/stores/keycloak'
|
||||||
|
|
||||||
const loginSchema = z.object({
|
const loginSchema = z.object({
|
||||||
name: z.string().min(3, 'Please enter a valid username'),
|
name: z.string().min(3, 'Please enter a valid username'),
|
||||||
@@ -39,10 +41,75 @@ async function onSubmit(data: LoginFormData) {
|
|||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
// const store = useKeycloak()
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
loggedIn: false
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onSSO() {
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
const initOptions = {
|
||||||
|
url: config.public.KEYCLOAK_URL,
|
||||||
|
realm: config.public.KEYCLOAK_REALM,
|
||||||
|
clientId: config.public.KEYCLOAK_ID,
|
||||||
|
onLoad: 'login-required'
|
||||||
|
}
|
||||||
|
|
||||||
|
const keycloak = new Keycloak(initOptions)
|
||||||
|
keycloak
|
||||||
|
.init({ onLoad: initOptions.onLoad })
|
||||||
|
.then((auth) => {
|
||||||
|
if (!auth) {
|
||||||
|
window.location.reload()
|
||||||
|
} else {
|
||||||
|
// store.setup(keycloak)
|
||||||
|
state.loggedIn = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (state.loggedIn) {
|
||||||
|
const result = await xfetch('/api/v1/authentication/login-fes', 'POST', {
|
||||||
|
data: keycloak,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
const { data: rawdata, meta } = result.body
|
||||||
|
if (meta.status === 'verified') {
|
||||||
|
login(rawdata)
|
||||||
|
navigateTo('/')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (result.errors) {
|
||||||
|
Object.entries(result.errors).forEach(
|
||||||
|
([field, errorInfo]: [string, any]) => (apiErrors.value[field] = errorInfo.message),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiErrors.value.general = result.error?.message || result.message || 'Login failed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const urlSSO =
|
||||||
|
// config.public.KEYCLOAK_ISSUER +
|
||||||
|
// '/protocol/openid-connect/auth?client_id=' +
|
||||||
|
// config.public.KEYCLOAK_ID +
|
||||||
|
// '&scope=openid%20email%20profile&response_type=code&redirect_uri=' +
|
||||||
|
// config.public.KEYCLOAK_LOGOUT_REDIRECT +
|
||||||
|
// '%2Fapi%2Fauth%2Fcallback%2Fkeycloak&state=AKf-WHWdL822V3LaNS5MSFzJ96-VHp6FUXlXxIAzXXM&code_challenge=nXOcGLLlA1NtXI4RM4hL59iP_I_yQAsUDd5sAOkKBF4&code_challenge_method=S256'
|
||||||
|
// await navigateTo(urlSSO,
|
||||||
|
// {
|
||||||
|
// open: { target: '_blank' },
|
||||||
|
// external: true
|
||||||
|
// })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AppAuthLogin :schema="loginSchema" :is-loading="isLoading" @submit="onSubmit" />
|
<AppAuthLogin :schema="loginSchema" :is-loading="isLoading" @submit="onSubmit" @sso="onSSO" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -7,10 +7,32 @@ export default defineNuxtConfig({
|
|||||||
API_ORIGIN: process.env.NUXT_API_ORIGIN || 'http://localhost:3000',
|
API_ORIGIN: process.env.NUXT_API_ORIGIN || 'http://localhost:3000',
|
||||||
VCLAIM: process.env.NUXT_API_VCLAIM || 'http://localhost:3000',
|
VCLAIM: process.env.NUXT_API_VCLAIM || 'http://localhost:3000',
|
||||||
VCLAIM_SWAGGER: process.env.NUXT_API_VCLAIM_SWAGGER || 'http://localhost:3000',
|
VCLAIM_SWAGGER: process.env.NUXT_API_VCLAIM_SWAGGER || 'http://localhost:3000',
|
||||||
|
//SSO
|
||||||
|
X_AP_CODE: process.env.X_AP_CODE || 'rssa-sso',
|
||||||
|
X_AP_SECRET_KEY: process.env.X_AP_SECRET_KEY || 'sapiperah',
|
||||||
|
SSO_CONFIRM_URL: process.env.SSO_CONFIRM_URL || 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/userinfo',
|
||||||
|
KEYCLOAK_ID: process.env.KEYCLOAK_ID || 'portal-simrs-new',
|
||||||
|
KEYCLOAK_SECRET: process.env.KEYCLOAK_SECRET || 'awoiehrw3w8942341k1ln4',
|
||||||
|
KEYCLOAK_ISSUER: process.env.KEYCLOAK_ISSUER || 'https://auth.dev.rssa.id/realms/sandbox',
|
||||||
|
KEYCLOAK_LOGOUT_REDIRECT: process.env.KEYCLOAK_LOGOUT_REDIRECT || 'http://localhost:3000',
|
||||||
|
//test
|
||||||
|
KEYCLOAK_REALM: process.env.KEYCLOAK_REALM || 'sandbox',
|
||||||
|
KEYCLOAK_URL: process.env.KEYCLOAK_URL || 'https://auth.dev.rssa.id/',
|
||||||
public: {
|
public: {
|
||||||
API_ORIGIN: process.env.NUXT_API_ORIGIN || 'http://localhost:3000',
|
API_ORIGIN: process.env.NUXT_API_ORIGIN || 'http://localhost:3000',
|
||||||
VCLAIM: process.env.NUXT_API_VCLAIM || 'http://localhost:3000',
|
VCLAIM: process.env.NUXT_API_VCLAIM || 'http://localhost:3000',
|
||||||
VCLAIM_SWAGGER: process.env.NUXT_API_VCLAIM_SWAGGER || 'http://localhost:3000',
|
VCLAIM_SWAGGER: process.env.NUXT_API_VCLAIM_SWAGGER || 'http://localhost:3000',
|
||||||
|
//SSO
|
||||||
|
X_AP_CODE: process.env.X_AP_CODE || 'rssa-sso',
|
||||||
|
X_AP_SECRET_KEY: process.env.X_AP_SECRET_KEY || 'sapiperah',
|
||||||
|
SSO_CONFIRM_URL: process.env.SSO_CONFIRM_URL || 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/userinfo',
|
||||||
|
KEYCLOAK_ID: process.env.KEYCLOAK_ID || 'portal-simrs-new',
|
||||||
|
KEYCLOAK_SECRET: process.env.KEYCLOAK_SECRET || 'awoiehrw3w8942341k1ln4',
|
||||||
|
KEYCLOAK_ISSUER: process.env.KEYCLOAK_ISSUER || 'https://auth.dev.rssa.id/realms/sandbox',
|
||||||
|
KEYCLOAK_LOGOUT_REDIRECT: process.env.KEYCLOAK_LOGOUT_REDIRECT || 'http://localhost:3000',
|
||||||
|
//test
|
||||||
|
KEYCLOAK_REALM: process.env.KEYCLOAK_REALM || 'sandbox',
|
||||||
|
KEYCLOAK_URL: process.env.KEYCLOAK_URL || 'https://auth.dev.rssa.id/',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "eslint --fix ."
|
"format": "eslint --fix ."
|
||||||
},
|
},
|
||||||
|
"main": "./lib/keycloak.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "^1.2.30",
|
"@iconify-json/lucide": "^1.2.30",
|
||||||
"@iconify-json/radix-icons": "^1.2.2",
|
"@iconify-json/radix-icons": "^1.2.2",
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
"embla-carousel": "^8.5.2",
|
"embla-carousel": "^8.5.2",
|
||||||
"embla-carousel-vue": "^8.5.2",
|
"embla-carousel-vue": "^8.5.2",
|
||||||
"h3": "^1.15.4",
|
"h3": "^1.15.4",
|
||||||
|
"keycloak-js": "^26.2.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"pinia-plugin-persistedstate": "^4.4.1",
|
"pinia-plugin-persistedstate": "^4.4.1",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
@@ -50,6 +52,7 @@
|
|||||||
"eslint-plugin-format": "^1.0.1",
|
"eslint-plugin-format": "^1.0.1",
|
||||||
"happy-dom": "^18.0.1",
|
"happy-dom": "^18.0.1",
|
||||||
"lucide-vue-next": "^0.482.0",
|
"lucide-vue-next": "^0.482.0",
|
||||||
|
"next-auth": "~4.21.1",
|
||||||
"nuxt": "^4.0.3",
|
"nuxt": "^4.0.3",
|
||||||
"playwright-core": "^1.54.2",
|
"playwright-core": "^1.54.2",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import { getRequestURL, readBody, setCookie } from 'h3'
|
||||||
|
|
||||||
|
// Function to verify JWT token with the userinfo endpoint
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
console.log("=================== MASUK FE SSO! ===================")
|
||||||
|
const body = await readBody(event)
|
||||||
|
const url = getRequestURL(event)
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
|
console.log("body: " + JSON.stringify(body))
|
||||||
|
|
||||||
|
// const apiSSOConfirm = 'https://auth.rssa.top/realms/sandbox/protocol/openid-connect/userinfo'
|
||||||
|
const apiSSOConfirm = config.public.SSO_CONFIRM_URL
|
||||||
|
|
||||||
|
const jwt = body.jwt
|
||||||
|
// const nip = body.nip
|
||||||
|
// const role = body.role
|
||||||
|
// const roleid = body.roleid
|
||||||
|
// const shift = body.shift
|
||||||
|
// const loginStatus = body.status_login
|
||||||
|
const token = 'Bearer ' + jwt
|
||||||
|
|
||||||
|
const res_sso = await fetch(apiSSOConfirm,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': token,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(res_sso)
|
||||||
|
if (res_sso.status === 200) {
|
||||||
|
const parts = jwt.split('.')
|
||||||
|
|
||||||
|
if (parts.count != 3) {
|
||||||
|
// return ['error' => 'Invalid JWT format'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = Buffer.from(strtr(parts[0], '-_', '+/'), 'base64').toString('utf8')
|
||||||
|
const payload = Buffer.from(strtr(parts[1], '-_', '+/'), 'base64').toString('utf8')
|
||||||
|
|
||||||
|
// const textDecoder = new TextDecoder('utf-8');
|
||||||
|
// // Decode the header and payload
|
||||||
|
// const decodedBinaryHead = atob(parts[0]);
|
||||||
|
// const decodedBinaryPayload = atob(parts[0]);
|
||||||
|
// const header = textDecoder.decode(Uint8Array.from(decodedBinaryHead, char => char.charCodeAt(0)));
|
||||||
|
// const payload = textDecoder.decode(Uint8Array.from(decodedBinaryPayload, char => char.charCodeAt(0)));
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
'header': header,
|
||||||
|
'payload': payload
|
||||||
|
};
|
||||||
|
|
||||||
|
const apiOrigin = config.public.API_ORIGIN
|
||||||
|
|
||||||
|
const cleanOrigin = apiOrigin.replace(/\/+$/, '')
|
||||||
|
const cleanPath = url.pathname.replace(/^\/api\//, '').replace(/^\/+/, '')
|
||||||
|
const externalUrl = `${cleanOrigin}/${cleanPath}${url.search}`
|
||||||
|
console.log("external url: " + externalUrl)
|
||||||
|
console.log("body: " + JSON.stringify(body))
|
||||||
|
|
||||||
|
const resp = await fetch(externalUrl,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: JSON.parse(payload).name,
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-AuthPartner-Code': config.public.X_AP_CODE,
|
||||||
|
'X-AuthPartner-SecretKey': config.public.X_AP_SECRET_KEY,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(resp)
|
||||||
|
// if (resp.status === 200) {
|
||||||
|
// const data = await resp.json()
|
||||||
|
|
||||||
|
// if (data?.data?.accessToken) {
|
||||||
|
// setCookie(event, 'authentication', data.data.accessToken, {
|
||||||
|
// path: '/',
|
||||||
|
// httpOnly: true,
|
||||||
|
// sameSite: 'strict',
|
||||||
|
// maxAge: 60 * 60 * 24,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// delete data.data.accessToken
|
||||||
|
// // return data
|
||||||
|
|
||||||
|
// const { login } = useUserStore()
|
||||||
|
// await login(resp.text())
|
||||||
|
// await navigateTo('/')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return new Response(await resp.text(), {
|
||||||
|
status: resp.status,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': resp.headers.get('content-type') || 'text/plain',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(await res_sso.text(), {
|
||||||
|
status: res_sso.status,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': res_sso.headers.get('content-type') || 'text/plain',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function strtr(str: string, fromChars: string, toChars: string) {
|
||||||
|
let result = str;
|
||||||
|
for (let i = 0; i < fromChars.length; i++) {
|
||||||
|
const fromChar = fromChars[i] || '_-';
|
||||||
|
// const toChar = toChars[i];
|
||||||
|
// Use a global regex to replace all occurrences of the character
|
||||||
|
result = result.replace(new RegExp(fromChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), toChars);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user