Skip to content

Commit

Permalink
feat: Add digiLocker module (#31)
Browse files Browse the repository at this point in the history
Co-authored-by: Sai Ranjit Tummalapalli <[email protected]>
  • Loading branch information
tusharbhayani and sairanjit authored Nov 28, 2024
1 parent 9850f16 commit 9000558
Show file tree
Hide file tree
Showing 9 changed files with 1,947 additions and 491 deletions.
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
42 changes: 42 additions & 0 deletions packages/digilocker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"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": {
"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": "*"
}
}
10 changes: 10 additions & 0 deletions packages/digilocker/src/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const DIGILOCKER_TOKEN_URL = 'https://api.digitallocker.gov.in/public/oauth2/1/token'
export const DIGILOCKER_AADHAAR = 'https://api.digitallocker.gov.in/public/oauth2/3/xml/eaadhaar'
export const DIGILOCKER_ISSUE_DOCUMENT = 'https://api.digitallocker.gov.in/public/oauth2/2/files/issued'
export const DIGILOCKER_FETCH_DOCUMENT = 'https://api.digitallocker.gov.in/public/oauth2/1/xml/'
export const DIGILOCKER_FETCH_FILE = 'https://api.digitallocker.gov.in/public/oauth2/1/file/'
export const DIGILOCKER_CLIENT_ID_URL_1 =
'https://api.digitallocker.gov.in/public/oauth2/1/authorize?response_type=code&client_id='
export const DIGILOCKER_REDIRECT_URL_2 = '&redirect_uri='
export const DIGILOCKER_CODE_CHALLENGE_URL_3 = '&state=adeya2024&code_challenge='
export const DIGILOCKER_CODE_CHALLENGE_METHOD_URL_4 = '&code_challenge_method=S256'
133 changes: 133 additions & 0 deletions packages/digilocker/src/digilocker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import axios from 'axios'
import { createHash } from 'crypto'

import {
DIGILOCKER_AADHAAR,
DIGILOCKER_CLIENT_ID_URL_1,
DIGILOCKER_CODE_CHALLENGE_METHOD_URL_4,
DIGILOCKER_CODE_CHALLENGE_URL_3,
DIGILOCKER_FETCH_DOCUMENT,
DIGILOCKER_FETCH_FILE,
DIGILOCKER_ISSUE_DOCUMENT,
DIGILOCKER_REDIRECT_URL_2,
DIGILOCKER_TOKEN_URL
} from './constant'

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 = `${DIGILOCKER_CLIENT_ID_URL_1}${client_id}${DIGILOCKER_REDIRECT_URL_2}${redirect_url}${DIGILOCKER_CODE_CHALLENGE_URL_3}${codeChallenge}${DIGILOCKER_CODE_CHALLENGE_METHOD_URL_4}`
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 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(DIGILOCKER_TOKEN_URL, 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 }> => {
try {
const response = await axios.get(DIGILOCKER_AADHAAR, {
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 }> => {
try {
const response = await axios.get(DIGILOCKER_ISSUE_DOCUMENT, {
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 = `${DIGILOCKER_FETCH_DOCUMENT}${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}` }
}
}

export const fetchDocument = async (uri: string, accessToken: string): Promise<{ message: string }> => {
const documentUrl = `${DIGILOCKER_FETCH_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

0 comments on commit 9000558

Please sign in to comment.