Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add digiLocker module #31

Merged
merged 6 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules
.DS_Store
build
build

*.tgz
43 changes: 43 additions & 0 deletions packages/digilocker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@adeya/digilocker",
"version": "0.0.0",
"license": "Apache-2.0",
"main": "build/index",
"source": "src/index",
"homepage": "https://github.com/credebl/adeya-sdk/tree/main/packages/digilocker",
"repository": {
"url": "https://github.com/credebl/adeya-sdk/tree/main/packages/digilocker",
"type": "git",
"directory": "packages/digilocker"
},
"publishConfig": {
"access": "public"
},
"files": [
"build"
],
"dependencies": {
"react-native": "0.72.5",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep react-native only in the peer dependencies

"axios": "^1.6.0",
"crypto-js": "^4.2.0",
"xml2js": "^0.6.2",
"uuid": "^9.0.0"
},
"scripts": {
"check-types": "pnpm compile --noEmit",
"build": "pnpm clean && pnpm compile",
"clean": "rimraf -rf ./build",
"compile": "tsc",
"release": "release-it"
},
"devDependencies": {
"@types/node": "^18.18.8",
"rimraf": "3.0.2",
"typescript": "~5.5.2",
"@types/xml2js": "~0.4.14",
"@types/uuid": "^9.0.2"
},
"peerDependencies": {
"react-native": "0.72.5"
}
}
127 changes: 127 additions & 0 deletions packages/digilocker/src/digilocker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import axios from 'axios'
import { createHash } from 'crypto'

export type AdeyaDigiLockerModuleOptions = {
client_id?: string | undefined
client_secret?: string | undefined
redirect_url?: string | undefined
authCode?: string | undefined
codeVerifier?: string | undefined
}

export const base64UrlEncodeWithoutPadding = (input: Buffer): string => {
return input.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_')
}

export const generateCodeChallenge = (codeVerifier: string): string => {
const hash = createHash('sha256').update(codeVerifier).digest()
return base64UrlEncodeWithoutPadding(hash)
}

export const initiateDigiLockerOAuth = async ({
client_id = '',
redirect_url = '',
codeVerifier = ''
}: AdeyaDigiLockerModuleOptions) => {
try {
const codeChallenge = generateCodeChallenge(codeVerifier)
const authUrl = `https://api.digitallocker.gov.in/public/oauth2/1/authorize?response_type=code&client_id=${client_id}&redirect_uri=${redirect_url}&state=adeya2024&code_challenge=${codeChallenge}&code_challenge_method=S256`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we those API URL and config in const file?

return authUrl
} catch (error) {
return error instanceof Error ? error : new Error('An unknown error occurred')
}
}

export const fetchDigiLockerToken = async ({
authCode = '',
client_id = '',
client_secret = '',
redirect_url = '',
codeVerifier = ''
}: AdeyaDigiLockerModuleOptions) => {
const tokenUrl = 'https://api.digitallocker.gov.in/public/oauth2/1/token'

const params =
`grant_type=authorization_code&` +
`code=${encodeURIComponent(authCode)}&` +
`client_id=${encodeURIComponent(client_id)}&` +
`client_secret=${encodeURIComponent(client_secret)}&` +
`redirect_uri=${encodeURIComponent(redirect_url)}&` +
`code_verifier=${encodeURIComponent(codeVerifier)}`

try {
const response = await axios.post(tokenUrl, params, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
return response.data
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
return { message: `Error fetching DigiLocker token: ${errorMessage}` }
}
}

export const fetchAadhaarData = async (accessToken: string): Promise<{ message: string }> => {
const aadhaarUrl = 'https://api.digitallocker.gov.in/public/oauth2/3/xml/eaadhaar'

try {
const response = await axios.get(aadhaarUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
return response.data
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
return { message: `Error fetching Aadhaar data: ${errorMessage}` }
}
}

export const fetchIssuedDocuments = async (accessToken: string): Promise<{ message: string }> => {
const issuedDocumentsUrl = 'https://api.digitallocker.gov.in/public/oauth2/2/files/issued'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same


try {
const response = await axios.get(issuedDocumentsUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
return response.data
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
return { message: `Error fetching issued documents: ${errorMessage}` }
}
}

export const fetchDocumentData = async (uri: string, accessToken: string): Promise<{ message: string }> => {
const documentUrl = `https://api.digitallocker.gov.in/public/oauth2/1/xml/${uri}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same


try {
const response = await axios.get(documentUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
return response.data
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
return { message: `Error fetching document data: ${errorMessage}` }
}
}

export const fetchDocument = async (uri: string, accessToken: string): Promise<{ message: string }> => {
const documentUrl = `https://api.digitallocker.gov.in/public/oauth2/1/file/${uri}`

try {
const response = await axios.get(documentUrl, {
headers: {
Authorization: `Bearer ${accessToken}`
}
})
return response.data
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred'
return { message: `Error fetching document data: ${errorMessage}` }
}
}
166 changes: 166 additions & 0 deletions packages/digilocker/src/digilockerDataParse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { parseStringPromise } from 'xml2js'

interface AadhaarData {
uid: string
dob: string
gender: string
name: string
co: string
country: string
district: string
locality: string
pincode: string
state: string
vtc: string
house: string
street: string
landmark: string
postOffice: string
photo: string
}

interface PANData {
panNumber: string
name: string
dob: string
gender: string
}

interface DrivingLicenseData {
licenseNumber: string
issuedAt: string
issueDate: string
expiryDate: string
dob: string
swd: string
swdIndicator: string
gender: string
presentAddressLine1: string
presentAddressLine2: string
presentAddressHouse: string
presentAddressLandmark: string
presentAddressLocality: string
presentAddressVtc: string
presentAddressDistrict: string
presentAddressPin: string
presentAddressState: string
presentAddressCountry: string
permanentAddressLine1: string
permanentAddressLine2: string
permanentAddressHouse: string
permanentAddressLandmark: string
permanentAddressLocality: string
permanentAddressVtc: string
permanentAddressDistrict: string
permanentAddressPin: string
permanentAddressState: string
permanentAddressCountry: string
licenseTypes: string
name: string
photo: string
}

const getOrDefault = (obj: any, path: string[], defaultValue = ''): string => {
return path.reduce((acc, key) => (acc && acc[key] ? acc[key] : defaultValue), obj)
}

export const parseAadhaarData = async (xmlString: string): Promise<AadhaarData | { error: string }> => {
try {
const result = await parseStringPromise(xmlString)
const uidData = result.Certificate.CertificateData[0].KycRes[0].UidData[0]
const poi = uidData.Poi[0]
const poa = uidData.Poa[0]
const photo = uidData.Pht[0] || ''

const aadhaarData: AadhaarData = {
uid: getOrDefault(uidData, ['$', 'uid']),
dob: getOrDefault(poi, ['$', 'dob']),
gender: getOrDefault(poi, ['$', 'gender']),
name: getOrDefault(poi, ['$', 'name']),
co: getOrDefault(poa, ['$', 'co']),
country: getOrDefault(poa, ['$', 'country']),
district: getOrDefault(poa, ['$', 'dist']),
locality: getOrDefault(poa, ['$', 'loc']),
pincode: getOrDefault(poa, ['$', 'pc']),
state: getOrDefault(poa, ['$', 'state']),
vtc: getOrDefault(poa, ['$', 'vtc']),
house: getOrDefault(poa, ['$', 'house']),
street: getOrDefault(poa, ['$', 'street']),
landmark: getOrDefault(poa, ['$', 'lm']),
postOffice: getOrDefault(poa, ['$', 'po']),
photo
}
return aadhaarData
} catch (error) {
return { error: 'Error parsing Aadhaar XML. Please check the input data.' }
}
}

export const parsePANData = async (xmlString: string): Promise<PANData | { error: string }> => {
try {
const result = await parseStringPromise(xmlString)

const certificate = result?.Certificate
const issuedTo = certificate?.IssuedTo?.[0]?.Person?.[0]

const panData: PANData = {
panNumber: certificate?.$?.number || '',
name: issuedTo?.$?.name || '',
dob: issuedTo?.$?.dob || '',
gender: issuedTo?.$?.gender || ''
}

return panData
} catch (error) {
return { error: 'Error parsing PAN XML. Please check the input data.' }
}
}

export const parseDrivingLicenseData = async (xmlString: string): Promise<DrivingLicenseData | { error: string }> => {
try {
const result = await parseStringPromise(xmlString)

const certificate = result?.Certificate
const issuedTo = certificate?.IssuedTo?.[0]?.Person?.[0]
const presentAddress = issuedTo?.Address?.[0]?.$ || {}
const permanentAddress = issuedTo?.Address2?.[0]?.$ || {}
const licenseTypes = certificate?.CertificateData[0]?.DrivingLicense[0]?.Categories[0]?.Category

const drivingLicenseData: DrivingLicenseData = {
licenseNumber: certificate?.$?.number || '',
issuedAt: certificate?.$?.issuedAt || '',
issueDate: certificate?.$?.issueDate || '',
expiryDate: certificate?.$?.expiryDate || '',
dob: issuedTo?.$?.dob || '',
swd: issuedTo?.$?.swd || '',
swdIndicator: issuedTo?.$?.swdIndicator || '',
name: issuedTo?.$?.name || '',
presentAddressLine1: presentAddress.line1 || '',
presentAddressLine2: presentAddress.line2 || '',
presentAddressHouse: presentAddress.house || '',
presentAddressLandmark: presentAddress.landmark || '',
presentAddressLocality: presentAddress.locality || '',
presentAddressVtc: presentAddress.vtc || '',
presentAddressDistrict: presentAddress.district || '',
presentAddressPin: presentAddress.pin || '',
presentAddressState: presentAddress.state || '',
presentAddressCountry: presentAddress.country || '',
permanentAddressLine1: permanentAddress.line1 || '',
permanentAddressLine2: permanentAddress.line2 || '',
permanentAddressHouse: permanentAddress.house || '',
permanentAddressLandmark: permanentAddress.landmark || '',
permanentAddressLocality: permanentAddress.locality || '',
permanentAddressVtc: permanentAddress.vtc || '',
permanentAddressDistrict: permanentAddress.district || '',
permanentAddressPin: permanentAddress.pin || '',
permanentAddressState: permanentAddress.state || '',
permanentAddressCountry: permanentAddress.country || '',
licenseTypes: licenseTypes.map((item: { $: { abbreviation: string } }) => item.$.abbreviation).join(', '),
gender: issuedTo?.$?.gender || '',
photo: issuedTo?.Photo?.[0]._ || ''
}
return drivingLicenseData
} catch (error) {
return { error: 'Error parsing Driving License XML. Please check the input data.' }
}
}
2 changes: 2 additions & 0 deletions packages/digilocker/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './digilocker'
export * from './digilockerDataParse'
7 changes: 7 additions & 0 deletions packages/digilocker/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./build"
},
"include": ["src"]
}
Loading