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

Adding new flutter mobile reference app repo #50

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 13 additions & 0 deletions generator/flutter/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"overrides": [
{
"files": ["generate.mjs", "confirm.mjs"],
"options": {
"printWidth": 120,
"semi": false,
"singleQuote": true,
"trailingComma": "all"
}
}
]
}
16 changes: 16 additions & 0 deletions generator/flutter/confirm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import inquirer from 'inquirer'

const { confirmed } = await inquirer.prompt([
{
default: true,
name: 'confirmed',
message: 'This will overwrite any changes made to the samples directory. Are you sure?',
type: 'confirm',
},
])

if (confirmed) {
process.exit(0)
} else {
process.exit(1)
}
111 changes: 111 additions & 0 deletions generator/flutter/generate.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import fs from 'fs/promises'
import { dirname, join, basename } from 'path'
import url from 'url'
import mkdirp from 'mkdirp'
import rimraf from 'rimraf'
import { generateAppInformation } from '../generateAppInformation.mjs'

const filesToIgnore = [ '.env', 'generator-config.json', '.gitkeep', 'keys', 'appInformation.json']
const pathsToOverwrite = []

const __dirname = dirname(url.fileURLToPath(import.meta.url))

async function generate() {
await generateAppInformation()
const rootPath = join(__dirname, '../..')
const samplesPath = join(rootPath, 'samples')
const generatorPath = join(rootPath, 'generator')
const flutterPath = join(generatorPath, 'flutter')
const overridesPath = join(flutterPath, 'overrides')
const templatePath = join(flutterPath, 'template')

const overrides = (await fs.readdir(overridesPath, { withFileTypes: true }))
.filter((i) => i.isDirectory())
.map((i) => i.name)
.sort()

console.log(`Detected samples: ${overrides.join(', ')}`)

for (const [i, sample] of overrides.entries()) {
console.log(`\nGenerating "${sample}" sample`)

const overridePath = join(overridesPath, sample)
const samplePath = join(samplesPath, sample)

let generatorConfig = {}
try {
generatorConfig = JSON.parse(
await fs.readFile(join(overridePath, 'generator-config.json'), {
encoding: 'utf-8',
}),
)
} catch (error) {
if (error.code !== 'ENOENT') {
throw error
}
}

console.log('Copying the template')
const pathsToDelete = (await fs.readdir(samplePath).catch(() => []))
.filter((file) => !filesToIgnore.includes(file))
.map((file) => join(samplePath, file))
await deletePath(pathsToDelete)
await merge(templatePath, samplePath, {
filter: (path) => !filesToIgnore.includes(basename(path)),
})

for (const path of pathsToOverwrite) {
if (await exists(join(overridePath, ...path))) {
console.log(`Deleting "${path.join('/')}" path from the template`)
await deletePath(join(samplePath, ...path))
}
}

console.log(`Applying overrides`)
await merge(overridePath, samplePath, {
filter: (path) => !filesToIgnore.includes(basename(path)),
})

const envPath = join(samplePath, '.env')
if (!(await exists(envPath))) {
await fs.cp(join(samplePath, '.env.example'), envPath)
}
}
}

async function merge(from, to, options) {
await mkdirp(join(to, '..'))

try {
await fs.cp(from, to, { recursive: true, ...options })
} catch (error) {
if (error.code === 'ENOENT') {
console.warn(`Warning: Source doesn't exist: ${from}`)
} else {
throw error
}
}
}

async function deletePath(path) {
await rimraf(path)
}

async function exists(path) {
try {
await fs.access(path)
return true
} catch {
return false
}
}

generate()
.catch((error) => {
console.error(error)
process.exit(1)
})
.then(() => {
console.log('\nDone!')
process.exit(0)
})
Empty file.
3 changes: 3 additions & 0 deletions generator/flutter/template/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
REDIRECT_URL=""
PROVIDER_CLIENT_ID=""
PROVIDER_ISSUER=""
25 changes: 25 additions & 0 deletions generator/flutter/template/appInformation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"affinidi-flutter-mobileapp": {
"redirectUris": {
"callbackUrl": "http://localhost:3000/affinidi-callback"
},
"metadata": {
"name": "Affinidi Login Integration using Flutter",
"description": "A code sample using Flutter framework to enable passwordless login using Affinidi IDP.",
"idp": {
"name": "Affinidi",
"link": "https://www.affinidi.com/product/affinidi-login"
},
"framework": {
"name": "Flutter",
"icon": "flutter-logo.svg",
"link": "https://flutter.dev/"
},
"language": {
"name": "Dart",
"icon": "dart-logo.svg",
"link": "https://dart.dev/"
}
}
}
}
3 changes: 3 additions & 0 deletions generator/flutter/template/backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PROVIDER_CLIENT_ID=""
PROVIDER_ISSUER=""
PROVIDER_REDIRECT_URL="http://localhost:3000/auth/callback"
46 changes: 46 additions & 0 deletions generator/flutter/template/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules
bin
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem
.idea

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

.env
db.json
keys

dist
package-lock.json
/.vscode
164 changes: 164 additions & 0 deletions generator/flutter/template/backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# Overview

An express server using NodeJS which exposes two endpoints to complete OAuth using PKCE Flow.

# @affinidi/passport-affinidi

@affinidi/passport-affinidi is a powerful module for authenticating users with `Affinidi Login` using the OIDC Code Grant flow. This strategy seamlessly integrates `Affinidi Login` into your Node.js applications. By leveraging Passport, you can effortlessly incorporate Affinidi authentication into any application or framework that supports Connect-style middleware, including Express.

This provider simplifies the process by creating an Affinidi OpenID client and registering two essential routes:

1. **Initialization Route**: The first GET route (defaulted to `/api/affinidi-auth/init`) returns the Affinidi authorization URL, which allows frontend applications to redirect to Affinidi Login flow.
2. **Completion Route**: The second POST route (defaulted to `/api/affinidi-auth/complete`) processes the response (code and state) from Affinidi authorization server, performs the exchange for the ID Token, and returns the user's profile.

## Installation

```
npm install @affinidi/passport-affinidi
```

## Usage

Here's how to use `@affinidi/passport-affinidi` in your Node.js application:

1. Import the affinidi provider

```
import { affinidiProvider } from '@affinidi/passport-affinidi'
```

2. Initialize the provider by passing your express server instance and configuration options, including the Affinidi's issuer, client ID, secret, and redirect URIs.

```
await affinidiProvider(app, {
id: "affinidi",
issuer: process.env.AFFINIDI_ISSUER,
client_id: process.env.AFFINIDI_CLIENT_ID,
pkce: true,
redirect_uris: ['http://localhost:3000/auth/callback']
});
```

## Run the App

1. Create Affinidi Login configuration with Authentication Mode as 'None', you can find same PEX & ID Token mapping [here](/profile-pex.json)

2. Update .env with the Login configuration settings

```
PROVIDER_CLIENT_ID="<CLIENT_ID>"
PROVIDER_ISSUER="<ISSUER>"
PROVIDER_REDIRECT_URL="http://localhost:3000/auth/callback"
```

5. Install the packages by executing the below command

```
npm install
```

4. Run the server

```
node index.js
```

API will be running on the [http://localhost:3001](http://localhost:3001)

## Sample API Calls using CURL

Here's how to initiate and complete the Affinidi from your frontend:

1. Initiate the Affinidi flow

```
curl --location 'http://localhost:3001/api/affinidi-auth/init'
```
Sample Response
```
{
"authorizationUrl":"https://427cc658-ddf8-4e5e-93b3-c038c13fac19.apse1.login.affinidi.io/oauth2/auth?client_id=ee144991-adc3-4f31-96bc-7876cdec6ea3&scope=openid&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fcallback&code_challenge=nclP4DJsfLwJft6KQ58A1gGaNy0g4kVWtli93t-F_Ho&code_challenge_method=S256&state=QaOxjjU-oxrIJvq6ca0BYxpVGK3YrfCN5nucnyrL7kE"
}
```

2. Complete the Affinidi flow from callback url and get user profile/error

```
curl --location 'http://localhost:3001/api/affinidi-auth/complete' \
--header 'Content-Type: application/json' \
--data '{
"code": "ory_ac_LfchXpWu3VkPWYU1AcpaCjbxGm3z_U0e6SF1QxwJBno.hnapsQj9n8fuBp-poZw1i-qCBmFn2jRKdEXD-Fd0U0c",
"state": "kb_2RYDCKUCPfohcpbshl_MaNwA8JYeSYY7QjqCYM0Y"
}'
```

Sample Response
```
{
"user": {
"acr": "0",
"at_hash": "MCyADFGco9OEb3lqBMqJrg",
"aud": [
"ee144991-adc3-4f31-96bc-7876cdec6ea3"
],
"auth_time": 1714400742,
"custom": [
{
"email": "[email protected]"
},
{
"issuer": "did:key:zQ3shtMGCU89kb2RMknNZcYGUcHW8P6Cq3CoQyvoDs7Qqh33N"
},
{
"phoneNumber": "99123334445"
},
{
"givenName": "Paramesh"
},
{
"familyName": "Kamarthi"
},
{
"nickname": "param"
},
{
"middleName": "-"
},
{
"birthdate": "1999-10-10"
},
{
"gender": "male"
},
{
"picture": "https://media.licdn.com/dms/image/C5103AQFwUMUmqZl1dw/profile-displayphoto-shrink_400_400/0/1517040412290?e=1716422400&v=beta&t=sZ-Of4mM7HZLiJIuHqTVarjJSRQqc0F1ZVdFeVJn_qc"
},
{
"formatted": "Big Layout, Bellandur"
},
{
"locality": "Bangalore"
},
{
"postalCode": "560103"
},
{
"country": "India"
},
{
"livenessCheckPassed": true
},
{
"did": "did:key:zQ3shqeuTqstXCVKL4peLhxmmLTjYSyoeCoPasx3vK3PQPf6i"
}
],
"exp": 1714401677,
"iat": 1714400777,
"iss": "https://427cc658-ddf8-4e5e-93b3-c038c13fac19.apse1.login.affinidi.io",
"jti": "5637d929-0cb5-4e1c-8adb-3edac0da6898",
"rat": 1714400714,
"sid": "7c0ba92f-75a8-4e70-bace-3868f3f55a54",
"sub": "did:key:zQ3shqeuTqstXCVKL4peLhxmmLTjYSyoeCoPasx3vK3PQPf6i"
}
}
```
Loading
Loading