-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🌒♻️ ↝ Merge pull request #21 from Signal-K/sta-24-setup-authenticatio…
…n-using-moralis 🌒♻️ ↝ Flask, Lens, Moralis authentication + Read/Write, graphql interactivity #16 , Signal-K/Silfur#24
- Loading branch information
Showing
67 changed files
with
7,563 additions
and
12,135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
env | ||
.vscode | ||
.vscode | ||
.env |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Make sure to initialise the Flask app with `pipenv` and run the command `export FLASK_APP=app.py` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,76 @@ | ||
from flask import Flask | ||
from flask import request | ||
from moralis import auth | ||
from flask_cors import CORS | ||
from flask import Flask, request, make_response, jsonify | ||
from thirdweb.types import LoginPayload | ||
from thirdweb import ThirdwebSDK | ||
from datetime import datetime, timedelta | ||
import os | ||
|
||
# Flask application setup | ||
app = Flask(__name__) | ||
CORS(app) | ||
apiKey = "kJfYYpmMmfKhvaWMdD3f3xMMb24B4MHBDDVrfjslkKgTilvMgdwr1bwKUr8vWdHH" # Move to env | ||
|
||
# Authentication routes -> move to auth.py later | ||
# Request a challenge when a user attempts to connect their wallet | ||
@app.route('/requestChallenge', methods=['GET']) | ||
def reqChallenge(): | ||
args = request.args # Fetch the arguments from the request | ||
|
||
# Get request body -> compare with data from Moralis | ||
body = { | ||
"domain": "sailors.skinetics.tech", | ||
"chainId": args.get("chainId"), | ||
"address": args.get("address"), | ||
"statement": "Please confirm authentication by signing this transaction", | ||
"uri": "https://ipfs.skinetics.tech/auth/1...", | ||
"expirationTime": "2023-01-01T00:00:00.000Z", | ||
"notBefore": "2020-01-01T00:00:00.000Z", | ||
"resources": ['https://docs.skinetics.tech/crypto/auth/signing'], | ||
"timeout": 30, | ||
} | ||
|
||
# Deliver the result to Moralis client | ||
result = auth.challenge.request_challenge_evm( | ||
api_key=apiKey, | ||
body=body, | ||
|
||
@app.route('/', methods=["GET"]) | ||
def index(): | ||
return "Hello World" | ||
|
||
@app.route('/login', methods=['POST']) | ||
def login(): | ||
private_key = os.environ.get("PRIVATE_KEY") | ||
|
||
if not private_key: | ||
print("Missing PRIVATE_KEY environment variable") | ||
return "Wallet private key not set", 400 | ||
|
||
sdk = ThirdwebSDK.from_private_key(private_key, 'mumbai') # Initialise the sdk using the wallet and on mumbai testnet chain | ||
payload = LoginPayload.from_json(request.json['payload']) | ||
|
||
# Generate access token using signed payload | ||
domain = 'sailors.skinetics.tech' | ||
token = sdk.auth.generate_auth_token(domain, payload) | ||
|
||
res = make_response() | ||
res.set_cookie( | ||
'access_token', | ||
token, | ||
path='/', | ||
httponly=True, | ||
secure=True, | ||
samesite='strict', | ||
) | ||
return res, 200 | ||
|
||
return result | ||
@app.route('/authenticate', methods=['POST']) | ||
def authenticate(): | ||
private_key = os.environ.get("PRIVATE_KEY") | ||
|
||
if not private_key: | ||
print("Missing PRIVATE_KEY environment variable") | ||
return "Wallet private key not set", 400 | ||
|
||
# Verify signature from user | ||
@app.route('/verifyChallenge', methods=['GET']) | ||
def verifyChallenge(): | ||
args = request.args | ||
sdk = ThirdwebSDK.from_private_key(private_key, 'mumbai') | ||
|
||
body = { # Request body | ||
"message": args.get("message"), | ||
"signature": args.get("signature"), | ||
}, | ||
# Get access token from cookies | ||
token = request.cookies.get('access_token') | ||
if not token: | ||
return 'Unauthorised', 401 | ||
|
||
domain = 'sailors.skinetics.tech' | ||
|
||
result = auth.challenge.verify_challenge_evm( | ||
api_key=apiKey, | ||
body=body, | ||
), | ||
try: | ||
address = sdk.auth.authenticate(domain, token) | ||
except: | ||
return "Unauthorized", 401 | ||
|
||
print(jsonify(address)) | ||
return jsonify(address), 200 | ||
|
||
return result | ||
@app.route('/logout', methods=['POST']) | ||
def logout(): | ||
res = make_response() | ||
res.set_cookie( | ||
'access_token', | ||
'none', | ||
expires=datetime.utcnow() + timedelta(second = 5) | ||
) | ||
return res, 200 | ||
|
||
# Initialising Flask application | ||
if __name__ == '__main__': | ||
app.run(host='127.0.0.1', port='5000', debug=True) | ||
@app.route('/helloworld') | ||
def helloworld(): | ||
return address |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { ConnectButton } from "web3uikit"; | ||
import Link from "next/link"; | ||
|
||
export default function Navbar() { | ||
return ( | ||
<ul> | ||
<Link href='/lens/'><li>Home</li></Link> | ||
<Link href='/lens/write-post'><li>Create Proposal</li></Link> | ||
<li> | ||
<div> | ||
<ConnectButton moralisAuth={false} /> | ||
</div> | ||
</li> | ||
</ul> | ||
) | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import ReactMarkdown from "react-markdown"; | ||
|
||
export default function PostContent({ post }) { | ||
return ( | ||
<div> | ||
<h1>{post.metadata.name}</h1> | ||
<ReactMarkdown>{post.metadata.content}</ReactMarkdown> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import Link from "next/link"; | ||
|
||
export default function PostFeed({ posts }) { | ||
return ( | ||
<div className="p-2"> | ||
{posts | ||
? posts.map((post) => <PostItem post={post} key={post.id} />) | ||
: null} | ||
</div> | ||
); | ||
} | ||
|
||
function PostItem({ post }) { | ||
let imageURL; | ||
if (post.metadata.image) { // IPFS gateway (URI/url) | ||
imageURL = post.metadata.image.replace("ipfs://", 'https://ipfs.io/ipfs'); // Replace ipfs link with a regular http ref.uri | ||
} | ||
|
||
return ( | ||
<div> | ||
<Link href={`/posts/${post.id}`}> | ||
<img src={imageURL} /> | ||
<h2>{post.metadata.name}</h2> | ||
</Link> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import { useForm } from "react-hook-form"; | ||
import { useLensContext } from '../context/lensContext'; | ||
import { | ||
createContentMetadata, | ||
getCreatePostQuery, // Compare to https://github.com/PatrickAlphaC/lens-blog | ||
lensClient, | ||
} from "../constants/lensConstants"; | ||
import { useWeb3Contract } from "react-moralis"; | ||
import lensAbi from '../contracts/lensABI.json'; | ||
import { | ||
lensHub, | ||
networkConfig, | ||
TRUE_BYTES, | ||
} from "../constants/contractConstants"; | ||
|
||
const PINATA_PIN_ENDPOINT = 'https://api.pinata.cloud/pinning/pinJSONToIPFS'; | ||
|
||
async function pinMetadataToPinata ( | ||
metadata, | ||
contentName, | ||
pinataApiKey, | ||
pinataApiSecret | ||
) { | ||
console.log('pinning metadata to pinata'); | ||
const data = JSON.stringify({ | ||
pinataMetadata: { name: contentName }, | ||
pinataContent: metadata, | ||
}); | ||
const config = { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": 'application/json', | ||
pinata_api_key: pinataApiKey, | ||
pinata_secret_api_key: pinataApiSecret, | ||
}, | ||
body: data, | ||
}; | ||
const response = await fetch(PINATA_PIN_ENDPOINT, config); | ||
const ipfsHash = (await response.json()).ipfsHash; | ||
console.log(`Stored content metadata with ${ipfsHash}`); | ||
return ipfsHash; | ||
} | ||
|
||
function PostForm () { | ||
const { profileId, token } = useLensContext(); | ||
const { register, errors, handleSubmit, formState, reset, watch } = useForm({ | ||
mode: 'onChange', | ||
}); | ||
const { runContractFunction } = useWeb3Contract(); | ||
|
||
const publishPost = async function ({ content, contentName, imageUri, imageType, pinataApiKey, pinataApiSecret }) { | ||
let fullContentUri; | ||
const contentMetadata = createContentMetadata(content, contentName, imageUri, imageType); | ||
const metadataIpfsHash = await pinMetadataToPinata(contentMetadata, contentName, pinataApiKey, pinataApiSecret); | ||
fullContentUri = `ipfs://${metadataIpfsHash}`; | ||
console.log(fullContentUri); | ||
|
||
// Post IPFS hash to Lens/blockchain | ||
const transactionParameters = [ | ||
profileId, | ||
fullContentUri, | ||
'0x23b9467334bEb345aAa6fd1545538F3d54436e96', // Free collect module contract address on Polygon (for now, all posts will be able to be collected without a fee). | ||
TRUE_BYTES, | ||
'0x17317F96f0C7a845FFe78c60B10aB15789b57Aaa', // Follower only reference module | ||
]; | ||
console.log(transactionParameters); | ||
const transactionOptions = { | ||
abi: lensAbi, | ||
contractAddress: '0xDb46d1Dc155634FbC732f92E853b10B288AD5a1d', // Lens Hub proxy contract address | ||
functionName: 'post', | ||
params: { | ||
vars: transactionParameters, | ||
}, | ||
}; | ||
|
||
await runContractFunction({ | ||
params: transactionOptions, | ||
onError: (error) => console.log(error), | ||
}); | ||
|
||
return ( | ||
"Hi" | ||
) | ||
}; | ||
|
||
return ( | ||
<form onSubmit = { handleSubmit( publishPost ) }> | ||
<input placeholder="Publication title" name='contentName' {...register("contentName", {maxLength: 100, minLength: 1, required: true})} /> | ||
<textarea placeholder="Write your proposal in markdown" name='content' {...register('content', { | ||
maxLength: 2500, minLength: 10, required: true | ||
})} /> | ||
<input placeholder="(optional) Image URI" name='imageUri' {...register("imageUri", { // Feature request -> ability to add multiple images? (Markdown ![]() styling as a temporary work around?) | ||
maxLength: 100, minLength: 1, required: false | ||
})} /> | ||
<input placeholder="(optional) Image type" name='imageType' {...register("imageType", { | ||
maxLength: 100, minLength: 1, required: false | ||
})} /> | ||
<input | ||
placeholder="(optional) Pinata.cloud API Key" | ||
name="pinataApiKey" | ||
{...register("pinataApiKey", { // Feature request -> This should be saved into a user's account (via Supabase) and retrieved whenever this page is loaded | ||
maxLength: 100, | ||
minLength: 1, | ||
required: false, | ||
})} | ||
/> | ||
<input | ||
placeholder="(optional) Pinata.cloud API Secret" | ||
name="pinataApiSecret" | ||
{...register("pinataApiSecret", { // Probably would need to implement Moralis <==> Supabase auth process (unless we switch back to Thirdweb SDK for auth & signing) so that the user address is -> Supabase | ||
maxLength: 100, | ||
minLength: 1, | ||
required: false, | ||
})} | ||
/> | ||
{errors ? <div>{errors.content?.message}</div> : <div></div> } | ||
{profileId && token ? ( | ||
<button type='submit'>Publish</button> | ||
) : <div>You need to sign in to make a post</div>} | ||
</form> | ||
) | ||
} | ||
|
||
export default function WritePost () { | ||
return ( | ||
<div><PostForm /></div> | ||
) | ||
} | ||
|
||
// Send form components to Flask -> Supabase. |
Oops, something went wrong.